QGIS API Documentation 3.99.0-Master (d31f8633d82)
Loading...
Searching...
No Matches
qgstextformat.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgstextformat.cpp
3 ---------------
4 begin : May 2020
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 "qgsconfig.h"
17#include "qgstextformat.h"
18
19#include "qgsapplication.h"
20#include "qgscolorutils.h"
21#include "qgsfontmanager.h"
22#include "qgsfontutils.h"
23#include "qgspainting.h"
24#include "qgspallabeling.h"
25#include "qgssymbollayerutils.h"
26#include "qgstextrenderer.h"
27#include "qgstextrenderer_p.h"
29#include "qgsunittypes.h"
30#include "qgsvectorlayer.h"
31
32#include <QFontDatabase>
33#include <QMimeData>
34#include <QScreen>
35#include <QString>
36#include <QWidget>
37
38using namespace Qt::StringLiterals;
39
41{
42 d = new QgsTextSettingsPrivate();
43}
44
46//****** IMPORTANT! editing this? make sure you update the move constructor too! *****
47 : mBufferSettings( other.mBufferSettings )
48 , mBackgroundSettings( other.mBackgroundSettings )
49 , mShadowSettings( other.mShadowSettings )
50 , mMaskSettings( other.mMaskSettings )
51 , mTextFontFamily( other.mTextFontFamily )
52 , mTextFontFound( other.mTextFontFound )
53 , d( other.d )
54 //****** IMPORTANT! editing this? make sure you update the move constructor too! *****
55{
56
57}
58
60 : mBufferSettings( std::move( other.mBufferSettings ) )
61 , mBackgroundSettings( std::move( other.mBackgroundSettings ) )
62 , mShadowSettings( std::move( other.mShadowSettings ) )
63 , mMaskSettings( std::move( other.mMaskSettings ) )
64 , mTextFontFamily( std::move( other.mTextFontFamily ) )
65 , mTextFontFound( other.mTextFontFound )
66 , d( std::move( other.d ) )
67{
68
69}
70
72{
73 if ( &other == this )
74 return *this;
75
76 //****** IMPORTANT! editing this? make sure you update the move assignment operator too! *****
77 d = other.d;
78 mBufferSettings = other.mBufferSettings;
79 mBackgroundSettings = other.mBackgroundSettings;
80 mShadowSettings = other.mShadowSettings;
81 mMaskSettings = other.mMaskSettings;
82 mTextFontFamily = other.mTextFontFamily;
83 mTextFontFound = other.mTextFontFound;
84 return *this;
85 //****** IMPORTANT! editing this? make sure you update the move assignment operator too! *****
86}
87
89{
90 if ( &other == this )
91 return *this;
92
93 d = std::move( other.d );
94 mBufferSettings = std::move( other.mBufferSettings );
95 mBackgroundSettings = std::move( other.mBackgroundSettings );
96 mShadowSettings = std::move( other.mShadowSettings );
97 mMaskSettings = std::move( other.mMaskSettings );
98 mTextFontFamily = std::move( other.mTextFontFamily );
99 mTextFontFound = other.mTextFontFound;
100 return *this;
101}
102
104{
105
106}
107
108bool QgsTextFormat::operator==( const QgsTextFormat &other ) const
109{
110 if ( d->isValid != other.isValid()
111 || d->textFont != other.font()
112 || namedStyle() != other.namedStyle()
113 || d->fontSizeUnits != other.sizeUnit()
114 || d->fontSizeMapUnitScale != other.sizeMapUnitScale()
115 || d->fontSize != other.size()
116 || d->textColor != other.color()
117 || d->opacity != other.opacity()
118 || d->blendMode != other.blendMode()
119 || d->multilineHeight != other.lineHeight()
120 || d->multilineHeightUnits != other.lineHeightUnit()
121 || d->orientation != other.orientation()
122 || d->previewBackgroundColor != other.previewBackgroundColor()
123 || d->allowHtmlFormatting != other.allowHtmlFormatting()
124 || d->forcedBold != other.forcedBold()
125 || d->forcedItalic != other.forcedItalic()
126 || d->capitalization != other.capitalization()
127 || d->tabStopDistance != other.tabStopDistance()
128 || d->tabPositions != other.tabPositions()
129 || d->tabStopDistanceUnits != other.tabStopDistanceUnit()
130 || d->tabStopDistanceMapUnitScale != other.tabStopDistanceMapUnitScale()
131 || mBufferSettings != other.mBufferSettings
132 || mBackgroundSettings != other.mBackgroundSettings
133 || mShadowSettings != other.mShadowSettings
134 || mMaskSettings != other.mMaskSettings
135 || d->families != other.families()
136 || d->mDataDefinedProperties != other.dataDefinedProperties() )
137 return false;
138
139 return true;
140}
141
142bool QgsTextFormat::operator!=( const QgsTextFormat &other ) const
143{
144 return !( *this == other );
145}
146
148{
149 return d->isValid;
150}
151
153{
154 d->isValid = true;
155}
156
158{
159 d->isValid = true;
160 return mBufferSettings;
161}
162
164{
165 d->isValid = true;
166 mBufferSettings = bufferSettings;
167}
168
170{
171 d->isValid = true;
172 return mBackgroundSettings;
173}
174
176{
177 d->isValid = true;
178 mBackgroundSettings = backgroundSettings;
179}
180
182{
183 d->isValid = true;
184 return mShadowSettings;
185}
186
188{
189 d->isValid = true;
190 mShadowSettings = shadowSettings;
191}
192
194{
195 d->isValid = true;
196 return mMaskSettings;
197}
198
200{
201 d->isValid = true;
202 mMaskSettings = maskSettings;
203}
204
206{
207 return d->textFont;
208}
209
210QFont QgsTextFormat::scaledFont( const QgsRenderContext &context, double scaleFactor, bool *isZeroSize ) const
211{
212 if ( isZeroSize )
213 *isZeroSize = false;
214
215 QFont font = d->textFont;
216 if ( scaleFactor == 1 )
217 {
218 int fontPixelSize = QgsTextRenderer::sizeToPixel( d->fontSize, context, d->fontSizeUnits,
219 d->fontSizeMapUnitScale );
220 if ( fontPixelSize == 0 )
221 {
222 if ( isZeroSize )
223 *isZeroSize = true;
224 return QFont();
225 }
226
227 font.setPixelSize( fontPixelSize );
228 }
229 else
230 {
231 double fontPixelSize = context.convertToPainterUnits( d->fontSize, d->fontSizeUnits, d->fontSizeMapUnitScale );
232 if ( qgsDoubleNear( fontPixelSize, 0 ) )
233 {
234 if ( isZeroSize )
235 *isZeroSize = true;
236 return QFont();
237 }
238 const int roundedPixelSize = static_cast< int >( std::round( scaleFactor * fontPixelSize + 0.5 ) );
239 font.setPixelSize( roundedPixelSize );
240 }
241
242 font.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( d->textFont.letterSpacing(), d->fontSizeUnits, d->fontSizeMapUnitScale ) * scaleFactor );
243 font.setWordSpacing( context.convertToPainterUnits( d->textFont.wordSpacing(), d->fontSizeUnits, d->fontSizeMapUnitScale ) * scaleFactor * scaleFactor );
244
245 if ( d->capitalization == Qgis::Capitalization::SmallCaps
246 || d->capitalization == Qgis::Capitalization::AllSmallCaps )
247 font.setCapitalization( QFont::SmallCaps );
248
249 return font;
250}
251
252void QgsTextFormat::setFont( const QFont &font )
253{
254 d->isValid = true;
255 d->textFont = font;
256 d->originalFontFamily.clear();
257}
258
260{
261 if ( !d->textNamedStyle.isEmpty() )
262 return d->textNamedStyle;
263
264 QFontDatabase db;
265 return db.styleString( d->textFont );
266}
267
268void QgsTextFormat::setNamedStyle( const QString &style )
269{
270 d->isValid = true;
271 QgsFontUtils::updateFontViaStyle( d->textFont, style );
272 d->textNamedStyle = style;
273}
274
276{
277 return d->forcedBold;
278}
279
281{
282 d->isValid = true;
283 d->textFont.setBold( forced );
284 d->forcedBold = true;
285}
286
288{
289 return d->forcedItalic;
290}
291
293{
294 d->isValid = true;
295 d->textFont.setItalic( forced );
296 d->forcedItalic = true;
297}
298
299QStringList QgsTextFormat::families() const
300{
301 return d->families;
302}
303
304void QgsTextFormat::setFamilies( const QStringList &families )
305{
306 d->isValid = true;
307 d->families = families;
308}
309
311{
312 return d->fontSizeUnits;
313}
314
316{
317 d->isValid = true;
318 d->fontSizeUnits = unit;
319}
320
322{
323 return d->fontSizeMapUnitScale;
324}
325
327{
328 d->isValid = true;
329 d->fontSizeMapUnitScale = scale;
330}
331
333{
334 return d->fontSize;
335}
336
338{
339 d->isValid = true;
340 d->fontSize = size;
341}
342
344{
345 return d->textColor;
346}
347
348void QgsTextFormat::setColor( const QColor &color )
349{
350 d->isValid = true;
351 d->textColor = color;
352}
353
355{
356 return d->opacity;
357}
358
359void QgsTextFormat::multiplyOpacity( double opacityFactor )
360{
361 if ( qgsDoubleNear( opacityFactor, 1.0 ) )
362 return;
363 d->opacity *= opacityFactor;
364 mBufferSettings.setOpacity( mBufferSettings.opacity() * opacityFactor );
365 mShadowSettings.setOpacity( mShadowSettings.opacity() * opacityFactor );
366 mBackgroundSettings.setOpacity( mBackgroundSettings.opacity() * opacityFactor );
367 mMaskSettings.setOpacity( mMaskSettings.opacity() * opacityFactor );
368}
369
371{
372 d->isValid = true;
373 d->opacity = opacity;
374}
375
377{
378 return d->textFont.stretch() > 0 ? d->textFont.stretch() : 100;
379}
380
382{
383 d->isValid = true;
384 d->textFont.setStretch( factor );
385}
386
387QPainter::CompositionMode QgsTextFormat::blendMode() const
388{
389 return d->blendMode;
390}
391
392void QgsTextFormat::setBlendMode( QPainter::CompositionMode mode )
393{
394 d->isValid = true;
395 d->blendMode = mode;
396}
397
399{
400 return d->multilineHeight;
401}
402
403void QgsTextFormat::setLineHeight( double height )
404{
405 d->isValid = true;
406 d->multilineHeight = height;
407}
408
410{
411 return d->multilineHeightUnits;
412}
413
415{
416 d->isValid = true;
417 d->multilineHeightUnits = unit;
418}
419
421{
422 return d->tabStopDistance;
423}
424
426{
427 d->isValid = true;
428 d->tabStopDistance = distance;
429}
430
431QList<QgsTextFormat::Tab> QgsTextFormat::tabPositions() const
432{
433 return d->tabPositions;
434}
435
436void QgsTextFormat::setTabPositions( const QList<Tab> &positions )
437{
438 d->isValid = true;
439 d->tabPositions = positions;
440}
441
443{
444 return d->tabStopDistanceUnits;
445}
446
448{
449 d->isValid = true;
450 d->tabStopDistanceUnits = unit;
451}
452
454{
455 return d->tabStopDistanceMapUnitScale;
456}
457
459{
460 d->isValid = true;
461 d->tabStopDistanceMapUnitScale = scale;
462}
463
465{
466 return d->orientation;
467}
468
470{
471 d->isValid = true;
472 d->orientation = orientation;
473}
474
476{
477 // bit of complexity here to maintain API..
478 return d->capitalization == Qgis::Capitalization::MixedCase && d->textFont.capitalization() != QFont::MixedCase
479 ? static_cast< Qgis::Capitalization >( d->textFont.capitalization() )
480 : d->capitalization ;
481}
482
484{
485 d->isValid = true;
486 d->capitalization = capitalization;
487 d->textFont.setCapitalization( capitalization == Qgis::Capitalization::SmallCaps || capitalization == Qgis::Capitalization::AllSmallCaps ? QFont::SmallCaps : QFont::MixedCase );
488}
489
491{
492 return d->allowHtmlFormatting;
493}
494
496{
497 d->isValid = true;
498 d->allowHtmlFormatting = allow;
499}
500
502{
503 return d->previewBackgroundColor;
504}
505
507{
508 d->isValid = true;
509 d->previewBackgroundColor = color;
510}
511
513{
514 d->isValid = true;
515 QFont appFont = QApplication::font();
516 d->originalFontFamily = QgsApplication::fontManager()->processFontFamilyName( layer->customProperty( u"labeling/fontFamily"_s, QVariant( appFont.family() ) ).toString() );
517 mTextFontFamily = d->originalFontFamily;
518 QString fontFamily = mTextFontFamily;
519 if ( mTextFontFamily != appFont.family() && !QgsFontUtils::fontFamilyMatchOnSystem( mTextFontFamily ) )
520 {
521 // trigger to notify about font family substitution
522 mTextFontFound = false;
523
524 // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
525 // currently only defaults to matching algorithm for resolving [foundry], if a font of similar family is found (default for QFont)
526
527 // for now, do not use matching algorithm for substitution if family not found, substitute default instead
528 fontFamily = appFont.family();
529 }
530 else
531 {
532 mTextFontFound = true;
533 }
534
535 if ( !layer->customProperty( u"labeling/fontSize"_s ).isValid() )
536 {
537 d->fontSize = appFont.pointSizeF();
538 }
539 else
540 {
541 d->fontSize = layer->customProperty( u"labeling/fontSize"_s ).toDouble();
542 }
543
544 if ( layer->customProperty( u"labeling/fontSizeUnit"_s ).toString().isEmpty() )
545 {
546 d->fontSizeUnits = layer->customProperty( u"labeling/fontSizeInMapUnits"_s, QVariant( false ) ).toBool() ?
548 }
549 else
550 {
551 bool ok = false;
552 d->fontSizeUnits = QgsUnitTypes::decodeRenderUnit( layer->customProperty( u"labeling/fontSizeUnit"_s ).toString(), &ok );
553 if ( !ok )
554 d->fontSizeUnits = Qgis::RenderUnit::Points;
555 }
556 if ( layer->customProperty( u"labeling/fontSizeMapUnitScale"_s ).toString().isEmpty() )
557 {
558 //fallback to older property
559 double oldMin = layer->customProperty( u"labeling/fontSizeMapUnitMinScale"_s, 0.0 ).toDouble();
560 d->fontSizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
561 double oldMax = layer->customProperty( u"labeling/fontSizeMapUnitMaxScale"_s, 0.0 ).toDouble();
562 d->fontSizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
563 }
564 else
565 {
566 d->fontSizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( u"labeling/fontSizeMapUnitScale"_s ).toString() );
567 }
568 int fontWeight = layer->customProperty( u"labeling/fontWeight"_s ).toInt();
569 bool fontItalic = layer->customProperty( u"labeling/fontItalic"_s ).toBool();
570 d->textFont = QgsFontUtils::createFont( fontFamily, d->fontSize, fontWeight, fontItalic );
571 d->textNamedStyle = QgsFontUtils::translateNamedStyle( layer->customProperty( u"labeling/namedStyle"_s, QVariant( "" ) ).toString() );
572 QgsFontUtils::updateFontViaStyle( d->textFont, d->textNamedStyle ); // must come after textFont.setPointSizeF()
573 d->capitalization = static_cast< Qgis::Capitalization >( layer->customProperty( u"labeling/fontCapitals"_s, QVariant( 0 ) ).toUInt() );
574 d->textFont.setUnderline( layer->customProperty( u"labeling/fontUnderline"_s ).toBool() );
575 d->textFont.setStrikeOut( layer->customProperty( u"labeling/fontStrikeout"_s ).toBool() );
576 d->textFont.setLetterSpacing( QFont::AbsoluteSpacing, layer->customProperty( u"labeling/fontLetterSpacing"_s, QVariant( 0.0 ) ).toDouble() );
577 d->textFont.setWordSpacing( layer->customProperty( u"labeling/fontWordSpacing"_s, QVariant( 0.0 ) ).toDouble() );
578 d->textColor = QgsTextRendererUtils::readColor( layer, u"labeling/textColor"_s, Qt::black, false );
579 if ( layer->customProperty( u"labeling/textOpacity"_s ).toString().isEmpty() )
580 {
581 d->opacity = ( 1 - layer->customProperty( u"labeling/textTransp"_s ).toInt() / 100.0 ); //0 -100
582 }
583 else
584 {
585 d->opacity = ( layer->customProperty( u"labeling/textOpacity"_s ).toDouble() );
586 }
587 d->blendMode = QgsPainting::getCompositionMode(
588 static_cast< Qgis::BlendMode >( layer->customProperty( u"labeling/blendMode"_s, QVariant( static_cast< int >( Qgis::BlendMode::Normal ) ) ).toUInt() ) );
589 d->multilineHeight = layer->customProperty( u"labeling/multilineHeight"_s, QVariant( 1.0 ) ).toDouble();
590 d->previewBackgroundColor = QgsTextRendererUtils::readColor( layer, u"labeling/previewBkgrdColor"_s, QColor( 255, 255, 255 ), false );
591
592 mBufferSettings.readFromLayer( layer );
593 mShadowSettings.readFromLayer( layer );
594 mBackgroundSettings.readFromLayer( layer );
595}
596
597void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
598{
599 d->isValid = true;
600 QDomElement textStyleElem;
601 if ( elem.nodeName() == "text-style"_L1 )
602 textStyleElem = elem;
603 else
604 textStyleElem = elem.firstChildElement( u"text-style"_s );
605 QFont appFont = QApplication::font();
606 d->originalFontFamily = QgsApplication::fontManager()->processFontFamilyName( textStyleElem.attribute( u"fontFamily"_s, appFont.family() ) );
607 mTextFontFamily = d->originalFontFamily;
608 QString fontFamily = mTextFontFamily;
609
610 const QDomElement familiesElem = textStyleElem.firstChildElement( u"families"_s );
611 const QDomNodeList familyNodes = familiesElem.childNodes();
612 QStringList families;
613 families.reserve( familyNodes.size() );
614 for ( int i = 0; i < familyNodes.count(); ++i )
615 {
616 const QDomElement familyElem = familyNodes.at( i ).toElement();
617 families << familyElem.attribute( u"name"_s );
618 }
619 d->families = families;
620
621 mTextFontFound = false;
622 QString matched;
623 if ( mTextFontFamily != appFont.family() && !QgsFontUtils::fontFamilyMatchOnSystem( mTextFontFamily ) )
624 {
625 if ( QgsApplication::fontManager()->tryToDownloadFontFamily( mTextFontFamily, matched ) )
626 {
627 mTextFontFound = true;
628 }
629 else
630 {
631 for ( const QString &family : std::as_const( families ) )
632 {
633 const QString processedFamily = QgsApplication::fontManager()->processFontFamilyName( family );
634 if ( QgsFontUtils::fontFamilyMatchOnSystem( processedFamily ) ||
635 QgsApplication::fontManager()->tryToDownloadFontFamily( processedFamily, matched ) )
636 {
637 mTextFontFound = true;
638 fontFamily = processedFamily;
639 break;
640 }
641 }
642
643 if ( !mTextFontFound )
644 {
645 // couldn't even find a matching font in the backup list -- substitute default instead
646 fontFamily = appFont.family();
647 }
648 }
649 }
650 else
651 {
652 mTextFontFound = true;
653 }
654
655 if ( !mTextFontFound )
656 {
657 context.pushMessage( QObject::tr( "Font “%1” not available on system" ).arg( mTextFontFamily ) );
658 }
659
660 if ( textStyleElem.hasAttribute( u"fontSize"_s ) )
661 {
662 d->fontSize = textStyleElem.attribute( u"fontSize"_s ).toDouble();
663 }
664 else
665 {
666 d->fontSize = appFont.pointSizeF();
667 }
668
669 if ( !textStyleElem.hasAttribute( u"fontSizeUnit"_s ) )
670 {
671 d->fontSizeUnits = textStyleElem.attribute( u"fontSizeInMapUnits"_s ).toUInt() == 0 ? Qgis::RenderUnit::Points
673 }
674 else
675 {
676 d->fontSizeUnits = QgsUnitTypes::decodeRenderUnit( textStyleElem.attribute( u"fontSizeUnit"_s ) );
677 }
678
679 if ( !textStyleElem.hasAttribute( u"fontSizeMapUnitScale"_s ) )
680 {
681 //fallback to older property
682 double oldMin = textStyleElem.attribute( u"fontSizeMapUnitMinScale"_s, u"0"_s ).toDouble();
683 d->fontSizeMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
684 double oldMax = textStyleElem.attribute( u"fontSizeMapUnitMaxScale"_s, u"0"_s ).toDouble();
685 d->fontSizeMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
686 }
687 else
688 {
689 d->fontSizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( textStyleElem.attribute( u"fontSizeMapUnitScale"_s ) );
690 }
691 int fontWeight = textStyleElem.attribute( u"fontWeight"_s ).toInt();
692 bool fontItalic = textStyleElem.attribute( u"fontItalic"_s ).toInt();
693 d->textFont = QgsFontUtils::createFont( fontFamily, d->fontSize, fontWeight, fontItalic );
694 d->textFont.setPointSizeF( d->fontSize ); //double precision needed because of map units
695 d->textNamedStyle = QgsFontUtils::translateNamedStyle( textStyleElem.attribute( u"namedStyle"_s ) );
696 QgsFontUtils::updateFontViaStyle( d->textFont, d->textNamedStyle ); // must come after textFont.setPointSizeF()
697 d->forcedBold = textStyleElem.attribute( u"forcedBold"_s ).toInt();
698 d->forcedItalic = textStyleElem.attribute( u"forcedItalic"_s ).toInt();
699 d->textFont.setUnderline( textStyleElem.attribute( u"fontUnderline"_s ).toInt() );
700 d->textFont.setStrikeOut( textStyleElem.attribute( u"fontStrikeout"_s ).toInt() );
701 d->textFont.setKerning( textStyleElem.attribute( u"fontKerning"_s, u"1"_s ).toInt() );
702 d->textFont.setLetterSpacing( QFont::AbsoluteSpacing, textStyleElem.attribute( u"fontLetterSpacing"_s, u"0"_s ).toDouble() );
703 d->textFont.setWordSpacing( textStyleElem.attribute( u"fontWordSpacing"_s, u"0"_s ).toDouble() );
704 d->textColor = QgsColorUtils::colorFromString( textStyleElem.attribute( u"textColor"_s, QgsColorUtils::colorToString( Qt::black ) ) );
705 if ( !textStyleElem.hasAttribute( u"textOpacity"_s ) )
706 {
707 d->opacity = ( 1 - textStyleElem.attribute( u"textTransp"_s ).toInt() / 100.0 ); //0 -100
708 }
709 else
710 {
711 d->opacity = ( textStyleElem.attribute( u"textOpacity"_s ).toDouble() );
712 }
713 d->textFont.setStretch( textStyleElem.attribute( u"stretchFactor"_s, u"100"_s ).toInt() );
714 d->orientation = QgsTextRendererUtils::decodeTextOrientation( textStyleElem.attribute( u"textOrientation"_s ) );
715 d->previewBackgroundColor = QgsColorUtils::colorFromString( textStyleElem.attribute( u"previewBkgrdColor"_s, QgsColorUtils::colorToString( Qt::white ) ) );
716
717 d->blendMode = QgsPainting::getCompositionMode(
718 static_cast< Qgis::BlendMode >( textStyleElem.attribute( u"blendMode"_s, QString::number( static_cast< int >( Qgis::BlendMode::Normal ) ) ).toUInt() ) );
719
720 if ( !textStyleElem.hasAttribute( u"multilineHeight"_s ) )
721 {
722 QDomElement textFormatElem = elem.firstChildElement( u"text-format"_s );
723 d->multilineHeight = textFormatElem.attribute( u"multilineHeight"_s, u"1"_s ).toDouble();
724 }
725 else
726 {
727 d->multilineHeight = textStyleElem.attribute( u"multilineHeight"_s, u"1"_s ).toDouble();
728 }
729 bool ok = false;
730 d->multilineHeightUnits = QgsUnitTypes::decodeRenderUnit( textStyleElem.attribute( u"multilineHeightUnit"_s, u"percent"_s ), &ok );
731
732 d->tabStopDistance = textStyleElem.attribute( u"tabStopDistance"_s, u"80"_s ).toDouble();
733 d->tabStopDistanceUnits = QgsUnitTypes::decodeRenderUnit( textStyleElem.attribute( u"tabStopDistanceUnit"_s, u"Point"_s ), &ok );
734 d->tabStopDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( textStyleElem.attribute( u"tabStopDistanceMapUnitScale"_s ) );
735
736 QList< Tab > tabPositions;
737 {
738 const QDomElement tabPositionsElem = textStyleElem.firstChildElement( u"tabPositions"_s );
739 const QDomNodeList tabNodes = tabPositionsElem.childNodes();
740 tabPositions.reserve( tabNodes.size() );
741 for ( int i = 0; i < tabNodes.count(); ++i )
742 {
743 const QDomElement tabElem = tabNodes.at( i ).toElement();
744 tabPositions << Tab( tabElem.attribute( u"position"_s ).toDouble() );
745 }
746 }
747 d->tabPositions = tabPositions;
748
749 if ( textStyleElem.hasAttribute( u"capitalization"_s ) )
750 d->capitalization = static_cast< Qgis::Capitalization >( textStyleElem.attribute( u"capitalization"_s, QString::number( static_cast< int >( Qgis::Capitalization::MixedCase ) ) ).toInt() );
751 else
752 d->capitalization = static_cast< Qgis::Capitalization >( textStyleElem.attribute( u"fontCapitals"_s, u"0"_s ).toUInt() );
753
754 if ( d->capitalization == Qgis::Capitalization::SmallCaps || d->capitalization == Qgis::Capitalization::AllSmallCaps )
755 d->textFont.setCapitalization( QFont::SmallCaps );
756
757 d->allowHtmlFormatting = textStyleElem.attribute( u"allowHtml"_s, u"0"_s ).toInt();
758
759 if ( textStyleElem.firstChildElement( u"text-buffer"_s ).isNull() )
760 {
761 mBufferSettings.readXml( elem );
762 }
763 else
764 {
765 mBufferSettings.readXml( textStyleElem );
766 }
767 if ( textStyleElem.firstChildElement( u"text-mask"_s ).isNull() )
768 {
769 mMaskSettings.readXml( elem );
770 }
771 else
772 {
773 mMaskSettings.readXml( textStyleElem );
774 }
775 if ( textStyleElem.firstChildElement( u"shadow"_s ).isNull() )
776 {
777 mShadowSettings.readXml( elem );
778 }
779 else
780 {
781 mShadowSettings.readXml( textStyleElem );
782 }
783 if ( textStyleElem.firstChildElement( u"background"_s ).isNull() )
784 {
785 mBackgroundSettings.readXml( elem, context );
786 }
787 else
788 {
789 mBackgroundSettings.readXml( textStyleElem, context );
790 }
791
792 QDomElement ddElem = textStyleElem.firstChildElement( u"dd_properties"_s );
793 if ( ddElem.isNull() )
794 {
795 ddElem = elem.firstChildElement( u"dd_properties"_s );
796 }
797 if ( !ddElem.isNull() )
798 {
799 d->mDataDefinedProperties.readXml( ddElem, QgsPalLayerSettings::propertyDefinitions() );
800 mBackgroundSettings.upgradeDataDefinedProperties( d->mDataDefinedProperties );
801 }
802 else
803 {
804 d->mDataDefinedProperties.clear();
805 }
806}
807
808QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
809{
810 // text style
811 QDomElement textStyleElem = doc.createElement( u"text-style"_s );
812 textStyleElem.setAttribute( u"fontFamily"_s, !d->originalFontFamily.isEmpty() ? d->originalFontFamily : d->textFont.family() );
813
814 QDomElement familiesElem = doc.createElement( u"families"_s );
815 for ( const QString &family : std::as_const( d->families ) )
816 {
817 QDomElement familyElem = doc.createElement( u"family"_s );
818 familyElem.setAttribute( u"name"_s, family );
819 familiesElem.appendChild( familyElem );
820 }
821 textStyleElem.appendChild( familiesElem );
822
823 textStyleElem.setAttribute( u"namedStyle"_s, QgsFontUtils::untranslateNamedStyle( d->textNamedStyle ) );
824 textStyleElem.setAttribute( u"fontSize"_s, d->fontSize );
825 textStyleElem.setAttribute( u"fontSizeUnit"_s, QgsUnitTypes::encodeUnit( d->fontSizeUnits ) );
826 textStyleElem.setAttribute( u"fontSizeMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( d->fontSizeMapUnitScale ) );
827 textStyleElem.setAttribute( u"fontWeight"_s, d->textFont.weight() );
828 textStyleElem.setAttribute( u"fontItalic"_s, d->textFont.italic() );
829 textStyleElem.setAttribute( u"fontStrikeout"_s, d->textFont.strikeOut() );
830 textStyleElem.setAttribute( u"fontUnderline"_s, d->textFont.underline() );
831 textStyleElem.setAttribute( u"forcedBold"_s, d->forcedBold );
832 textStyleElem.setAttribute( u"forcedItalic"_s, d->forcedItalic );
833 textStyleElem.setAttribute( u"textColor"_s, QgsColorUtils::colorToString( d->textColor ) );
834 textStyleElem.setAttribute( u"previewBkgrdColor"_s, QgsColorUtils::colorToString( d->previewBackgroundColor ) );
835 textStyleElem.setAttribute( u"fontLetterSpacing"_s, d->textFont.letterSpacing() );
836 textStyleElem.setAttribute( u"fontWordSpacing"_s, d->textFont.wordSpacing() );
837 textStyleElem.setAttribute( u"fontKerning"_s, d->textFont.kerning() );
838 textStyleElem.setAttribute( u"textOpacity"_s, d->opacity );
839 if ( d->textFont.stretch() > 0 )
840 textStyleElem.setAttribute( u"stretchFactor"_s, d->textFont.stretch() );
841 textStyleElem.setAttribute( u"textOrientation"_s, QgsTextRendererUtils::encodeTextOrientation( d->orientation ) );
842 textStyleElem.setAttribute( u"blendMode"_s, static_cast< int >( QgsPainting::getBlendModeEnum( d->blendMode ) ) );
843 textStyleElem.setAttribute( u"multilineHeight"_s, d->multilineHeight );
844 textStyleElem.setAttribute( u"multilineHeightUnit"_s, QgsUnitTypes::encodeUnit( d->multilineHeightUnits ) );
845
846 textStyleElem.setAttribute( u"tabStopDistance"_s, d->tabStopDistance );
847 textStyleElem.setAttribute( u"tabStopDistanceUnit"_s, QgsUnitTypes::encodeUnit( d->tabStopDistanceUnits ) );
848 textStyleElem.setAttribute( u"tabStopDistanceMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( d->tabStopDistanceMapUnitScale ) );
849
850 if ( !d->tabPositions.empty() )
851 {
852 QDomElement tabPositionsElem = doc.createElement( u"tabPositions"_s );
853 for ( const Tab &tab : std::as_const( d->tabPositions ) )
854 {
855 QDomElement tabElem = doc.createElement( u"tab"_s );
856 tabElem.setAttribute( u"position"_s, tab.position() );
857 tabPositionsElem.appendChild( tabElem );
858 }
859 textStyleElem.appendChild( tabPositionsElem );
860 }
861
862 textStyleElem.setAttribute( u"allowHtml"_s, d->allowHtmlFormatting ? u"1"_s : u"0"_s );
863 textStyleElem.setAttribute( u"capitalization"_s, QString::number( static_cast< int >( d->capitalization ) ) );
864
865 QDomElement ddElem = doc.createElement( u"dd_properties"_s );
866 d->mDataDefinedProperties.writeXml( ddElem, QgsPalLayerSettings::propertyDefinitions() );
867
868 textStyleElem.appendChild( mBufferSettings.writeXml( doc ) );
869 textStyleElem.appendChild( mMaskSettings.writeXml( doc ) );
870 textStyleElem.appendChild( mBackgroundSettings.writeXml( doc, context ) );
871 textStyleElem.appendChild( mShadowSettings.writeXml( doc ) );
872 textStyleElem.appendChild( ddElem );
873
874 return textStyleElem;
875}
876
878{
879 //set both the mime color data, and the text (format settings).
880
881 QMimeData *mimeData = new QMimeData;
882 mimeData->setColorData( QVariant( color() ) );
883
884 QgsReadWriteContext rwContext;
885 QDomDocument textDoc;
886 QDomElement textElem = writeXml( textDoc, rwContext );
887 textDoc.appendChild( textElem );
888 mimeData->setText( textDoc.toString() );
889
890 return mimeData;
891}
892
894{
895 QgsTextFormat format;
896 format.setFont( font );
897 if ( font.pointSizeF() > 0 )
898 {
899 format.setSize( font.pointSizeF() );
901 }
902 else if ( font.pixelSize() > 0 )
903 {
904 format.setSize( font.pixelSize() );
906 }
907
908 return format;
909}
910
912{
913 QFont f = font();
914 switch ( sizeUnit() )
915 {
917 f.setPointSizeF( size() );
918 break;
919
921 f.setPointSizeF( size() * 2.83464567 );
922 break;
923
925 f.setPointSizeF( size() * 72 );
926 break;
927
929 f.setPixelSize( static_cast< int >( std::round( size() ) ) );
930 break;
931
936 // no meaning here
937 break;
938 }
939 return f;
940}
941
942QgsTextFormat QgsTextFormat::fromMimeData( const QMimeData *data, bool *ok )
943{
944 if ( ok )
945 *ok = false;
946 QgsTextFormat format;
947 if ( !data )
948 return format;
949
950 QString text = data->text();
951 if ( !text.isEmpty() )
952 {
953 QDomDocument doc;
954 QDomElement elem;
955 QgsReadWriteContext rwContext;
956
957 if ( doc.setContent( text ) )
958 {
959 elem = doc.documentElement();
960
961 format.readXml( elem, rwContext );
962 if ( ok )
963 *ok = true;
964 return format;
965 }
966 }
967 return format;
968}
969
971{
972 if ( d->blendMode != QPainter::CompositionMode_SourceOver )
973 return true;
974
975 if ( mBufferSettings.enabled() && mBufferSettings.blendMode() != QPainter::CompositionMode_SourceOver )
976 return true;
977
978 if ( mBackgroundSettings.enabled() && mBackgroundSettings.blendMode() != QPainter::CompositionMode_SourceOver )
979 return true;
980
981 if ( mShadowSettings.enabled() && mShadowSettings.blendMode() != QPainter::CompositionMode_SourceOver )
982 return true;
983
984 return false;
985}
986
988{
989 if ( d->blendMode != QPainter::CompositionMode_SourceOver )
990 return true;
991
992 if ( mBufferSettings.enabled() && mBufferSettings.blendMode() != QPainter::CompositionMode_SourceOver )
993 return true;
994
995 if ( mBackgroundSettings.enabled() && mBackgroundSettings.blendMode() != QPainter::CompositionMode_SourceOver )
996 return true;
997
998 if ( mShadowSettings.enabled() && mShadowSettings.blendMode() != QPainter::CompositionMode_SourceOver )
999 return true;
1000
1001 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontBlendMode )
1002 || d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowBlendMode )
1003 || d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferBlendMode )
1004 || d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeBlendMode ) )
1005 return true;
1006
1007 return false;
1008}
1009
1011{
1012 d->isValid = true;
1013 return d->mDataDefinedProperties;
1014}
1015
1017{
1018 return d->mDataDefinedProperties;
1019}
1020
1021QSet<QString> QgsTextFormat::referencedFields( const QgsRenderContext &context ) const
1022{
1023 QSet< QString > fields = d->mDataDefinedProperties.referencedFields( context.expressionContext(), true );
1024 fields.unite( mBufferSettings.referencedFields( context ) );
1025 fields.unite( mBackgroundSettings.referencedFields( context ) );
1026 fields.unite( mShadowSettings.referencedFields( context ) );
1027 fields.unite( mMaskSettings.referencedFields( context ) );
1028 return fields;
1029}
1030
1032{
1033 d->isValid = true;
1034 d->mDataDefinedProperties = collection;
1035}
1036
1038{
1039 d->isValid = true;
1040 if ( !d->mDataDefinedProperties.hasActiveProperties() )
1041 return;
1042
1043 QString ddFontFamily;
1044 context.expressionContext().setOriginalValueVariable( d->textFont.family() );
1045 QVariant exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::Family, context.expressionContext() );
1046 if ( !QgsVariantUtils::isNull( exprVal ) )
1047 {
1048 QString family = exprVal.toString().trimmed();
1050 if ( d->textFont.family() != family )
1051 {
1052 // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
1053 // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
1054 if ( QgsFontUtils::fontFamilyOnSystem( family ) )
1055 {
1056 ddFontFamily = family;
1057 }
1058 }
1059 }
1060
1061 // data defined named font style?
1062 QString ddFontStyle;
1063 context.expressionContext().setOriginalValueVariable( d->textNamedStyle );
1064 exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontStyle, context.expressionContext() );
1065 if ( !QgsVariantUtils::isNull( exprVal ) )
1066 {
1067 QString fontstyle = exprVal.toString().trimmed();
1068 ddFontStyle = fontstyle;
1069 }
1070
1071 bool ddBold = false;
1072 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Bold ) )
1073 {
1074 context.expressionContext().setOriginalValueVariable( d->textFont.bold() );
1075 ddBold = d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Bold, context.expressionContext(), false ) ;
1076 }
1077
1078 bool ddItalic = false;
1079 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Italic ) )
1080 {
1081 context.expressionContext().setOriginalValueVariable( d->textFont.italic() );
1082 ddItalic = d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Italic, context.expressionContext(), false );
1083 }
1084
1085 // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
1086 // (currently defaults to what has been read in from layer settings)
1087 QFont newFont;
1088 QFontDatabase fontDb;
1089 QFont appFont = QApplication::font();
1090 bool newFontBuilt = false;
1091 if ( ddBold || ddItalic )
1092 {
1093 // new font needs built, since existing style needs removed
1094 newFont = QgsFontUtils::createFont( !ddFontFamily.isEmpty() ? ddFontFamily : d->textFont.family() );
1095 newFontBuilt = true;
1096 newFont.setBold( ddBold );
1097 newFont.setItalic( ddItalic );
1098 }
1099 else if ( !ddFontStyle.isEmpty()
1100 && ddFontStyle.compare( "Ignore"_L1, Qt::CaseInsensitive ) != 0 )
1101 {
1102 if ( !ddFontFamily.isEmpty() )
1103 {
1104 // both family and style are different, build font from database
1105 QFont styledfont = fontDb.font( ddFontFamily, ddFontStyle, appFont.pointSize() );
1106 if ( appFont != styledfont )
1107 {
1108 newFont = styledfont;
1109 newFontBuilt = true;
1110 }
1111 }
1112
1113 // update the font face style
1114 QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : d->textFont, ddFontStyle );
1115 }
1116 else if ( !ddFontFamily.isEmpty() )
1117 {
1118 if ( ddFontStyle.compare( "Ignore"_L1, Qt::CaseInsensitive ) != 0 )
1119 {
1120 // just family is different, build font from database
1121 QFont styledfont = fontDb.font( ddFontFamily, d->textNamedStyle, appFont.pointSize() );
1122 if ( appFont != styledfont )
1123 {
1124 newFont = styledfont;
1125 newFontBuilt = true;
1126 }
1127 }
1128 else
1129 {
1130 newFont = QgsFontUtils::createFont( ddFontFamily );
1131 newFontBuilt = true;
1132 }
1133 }
1134
1135 if ( newFontBuilt )
1136 {
1137 // copy over existing font settings
1138 newFont.setUnderline( d->textFont.underline() );
1139 newFont.setStrikeOut( d->textFont.strikeOut() );
1140 newFont.setWordSpacing( d->textFont.wordSpacing() );
1141 newFont.setLetterSpacing( QFont::AbsoluteSpacing, d->textFont.letterSpacing() );
1142 d->textFont = newFont;
1143 }
1144
1145 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Underline ) )
1146 {
1147 context.expressionContext().setOriginalValueVariable( d->textFont.underline() );
1148 d->textFont.setUnderline( d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Underline, context.expressionContext(), d->textFont.underline() ) );
1149 }
1150
1151 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Strikeout ) )
1152 {
1153 context.expressionContext().setOriginalValueVariable( d->textFont.strikeOut() );
1154 d->textFont.setStrikeOut( d->mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Strikeout, context.expressionContext(), d->textFont.strikeOut() ) );
1155 }
1156
1157 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Color ) )
1158 {
1160 d->textColor = d->mDataDefinedProperties.valueAsColor( QgsPalLayerSettings::Property::Color, context.expressionContext(), d->textColor );
1161 }
1162
1163 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Size ) )
1164 {
1166 d->fontSize = d->mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::Size, context.expressionContext(), d->fontSize );
1167 }
1168
1169 exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontSizeUnit, context.expressionContext() );
1170 if ( !QgsVariantUtils::isNull( exprVal ) )
1171 {
1172 QString units = exprVal.toString();
1173 if ( !units.isEmpty() )
1174 {
1175 bool ok;
1177 if ( ok )
1178 d->fontSizeUnits = res;
1179 }
1180 }
1181
1182 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontOpacity ) )
1183 {
1184 context.expressionContext().setOriginalValueVariable( d->opacity * 100 );
1185 const QVariant val = d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontOpacity, context.expressionContext(), d->opacity * 100 );
1186 if ( !QgsVariantUtils::isNull( val ) )
1187 {
1188 d->opacity = val.toDouble() / 100.0;
1189 }
1190 }
1191
1192 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStretchFactor ) )
1193 {
1194 context.expressionContext().setOriginalValueVariable( d->textFont.stretch() );
1195 const QVariant val = d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontStretchFactor, context.expressionContext(), d->textFont.stretch() );
1196 if ( !QgsVariantUtils::isNull( val ) )
1197 {
1198 d->textFont.setStretch( val.toInt() );
1199 }
1200 }
1201
1202 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TextOrientation ) )
1203 {
1204 const QString encoded = QgsTextRendererUtils::encodeTextOrientation( d->orientation );
1205 context.expressionContext().setOriginalValueVariable( encoded );
1206 d->orientation = QgsTextRendererUtils::decodeTextOrientation( d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::TextOrientation, context.expressionContext(), encoded ).toString() );
1207 }
1208
1209 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontLetterSpacing ) )
1210 {
1211 context.expressionContext().setOriginalValueVariable( d->textFont.letterSpacing() );
1212 const QVariant val = d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontLetterSpacing, context.expressionContext(), d->textFont.letterSpacing() );
1213 if ( !QgsVariantUtils::isNull( val ) )
1214 {
1215 d->textFont.setLetterSpacing( QFont::AbsoluteSpacing, val.toDouble() );
1216 }
1217 }
1218
1219 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontWordSpacing ) )
1220 {
1221 context.expressionContext().setOriginalValueVariable( d->textFont.wordSpacing() );
1222 const QVariant val = d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontWordSpacing, context.expressionContext(), d->textFont.wordSpacing() );
1223 if ( !QgsVariantUtils::isNull( val ) )
1224 {
1225 d->textFont.setWordSpacing( val.toDouble() );
1226 }
1227 }
1228
1229 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TabStopDistance ) )
1230 {
1231 context.expressionContext().setOriginalValueVariable( d->tabStopDistance );
1232 const QVariant val = d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::TabStopDistance, context.expressionContext(), d->tabStopDistance );
1233 if ( !QgsVariantUtils::isNull( val ) )
1234 {
1235 if ( val.userType() == QMetaType::Type::QVariantList )
1236 {
1237 const QVariantList parts = val.toList();
1238 d->tabPositions.clear();
1239 d->tabPositions.reserve( parts.size() );
1240 for ( const QVariant &part : parts )
1241 {
1242 d->tabPositions.append( Tab( part.toDouble() ) );
1243 }
1244 }
1245 else if ( val.userType() == QMetaType::Type::QStringList )
1246 {
1247 const QStringList parts = val.toStringList();
1248 d->tabPositions.clear();
1249 d->tabPositions.reserve( parts.size() );
1250 for ( const QString &part : parts )
1251 {
1252 d->tabPositions.append( Tab( part.toDouble() ) );
1253 }
1254 }
1255 else
1256 {
1257 d->tabPositions.clear();
1258 d->tabStopDistance = val.toDouble();
1259 }
1260 }
1261 }
1262
1263 if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontBlendMode ) )
1264 {
1265 exprVal = d->mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontBlendMode, context.expressionContext() );
1266 QString blendstr = exprVal.toString().trimmed();
1267 if ( !blendstr.isEmpty() )
1268 d->blendMode = QgsSymbolLayerUtils::decodeBlendMode( blendstr );
1269 }
1270
1271 mShadowSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties );
1272 mBackgroundSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties );
1273 mBufferSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties );
1274 mMaskSettings.updateDataDefinedProperties( context, d->mDataDefinedProperties );
1275}
1276
1277QPixmap QgsTextFormat::textFormatPreviewPixmap( const QgsTextFormat &format, QSize size, const QString &previewText, int padding, const QgsScreenProperties &screen )
1278{
1279 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
1280 QgsTextFormat tempFormat = format;
1281 QPixmap pixmap( size * devicePixelRatio );
1282 pixmap.fill( Qt::transparent );
1283 pixmap.setDevicePixelRatio( devicePixelRatio );
1284
1285 QPainter painter;
1286 painter.begin( &pixmap );
1287
1288 painter.setRenderHint( QPainter::Antialiasing );
1289
1290 const QRectF rect( 0, 0, size.width(), size.height() );
1291
1292 // shameless eye candy - use a subtle gradient when drawing background
1293 painter.setPen( Qt::NoPen );
1294 QColor background1 = tempFormat.previewBackgroundColor();
1295 if ( ( background1.lightnessF() < 0.7 ) )
1296 {
1297 background1 = background1.darker( 125 );
1298 }
1299 else
1300 {
1301 background1 = background1.lighter( 125 );
1302 }
1303 QColor background2 = tempFormat.previewBackgroundColor();
1304 QLinearGradient linearGrad( QPointF( 0, 0 ), QPointF( 0, rect.height() ) );
1305 linearGrad.setColorAt( 0, background1 );
1306 linearGrad.setColorAt( 1, background2 );
1307 painter.setBrush( QBrush( linearGrad ) );
1308 if ( size.width() > 30 )
1309 {
1310 painter.drawRoundedRect( rect, 6, 6 );
1311 }
1312 else
1313 {
1314 // don't use rounded rect for small previews
1315 painter.drawRect( rect );
1316 }
1317 painter.setBrush( Qt::NoBrush );
1318 painter.setPen( Qt::NoPen );
1319 padding += 1; // move text away from background border
1320
1321 QgsRenderContext context;
1322 QgsMapToPixel newCoordXForm;
1323 newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
1324 context.setMapToPixel( newCoordXForm );
1325
1326 if ( screen.isValid() )
1327 {
1328 screen.updateRenderContextForScreen( context );
1329 }
1330 else
1331 {
1332 QWidget *activeWindow = QApplication::activeWindow();
1333 if ( QScreen *screen = activeWindow ? activeWindow->screen() : nullptr )
1334 {
1335 context.setScaleFactor( screen->physicalDotsPerInch() / 25.4 );
1336 context.setDevicePixelRatio( screen->devicePixelRatio() );
1337 }
1338 else
1339 {
1340 context.setScaleFactor( 96.0 / 25.4 );
1341 context.setDevicePixelRatio( 1.0 );
1342 }
1343 }
1344
1347 context.setPainter( &painter );
1349
1350 // slightly inset text to account for buffer/background
1351 const double fontSize = context.convertToPainterUnits( tempFormat.size(), tempFormat.sizeUnit(), tempFormat.sizeMapUnitScale() );
1352 double xtrans = 0;
1353 if ( tempFormat.buffer().enabled() )
1354 xtrans = tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
1355 ? fontSize * tempFormat.buffer().size() / 100
1356 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
1357 if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
1358 xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1359
1360 double ytrans = 0.0;
1361 if ( tempFormat.buffer().enabled() )
1362 ytrans = std::max( ytrans, tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
1363 ? fontSize * tempFormat.buffer().size() / 100
1364 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() ) );
1365 if ( tempFormat.background().enabled() )
1366 ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1367
1368 const QStringList text = QStringList() << ( previewText.isEmpty() ? QObject::tr( "Aa" ) : previewText );
1369 const double textHeight = QgsTextRenderer::textHeight( context, tempFormat, text, Qgis::TextLayoutMode::Rectangle );
1370 QRectF textRect = rect;
1371 textRect.setLeft( xtrans + padding );
1372 textRect.setWidth( rect.width() - xtrans - 2 * padding );
1373
1374 if ( textRect.width() > 2000 )
1375 textRect.setWidth( 2000 - 2 * padding );
1376
1377 const double bottom = textRect.height() / 2 + textHeight / 2;
1378 textRect.setTop( bottom - textHeight );
1379 textRect.setBottom( bottom );
1380
1381 QgsTextRenderer::drawText( textRect, 0, Qgis::TextHorizontalAlignment::Center, text, context, tempFormat );
1382
1383 // draw border on top of text
1384 painter.setBrush( Qt::NoBrush );
1385 painter.setPen( QPen( tempFormat.previewBackgroundColor().darker( 150 ), 0 ) );
1386 if ( size.width() > 30 )
1387 {
1388 painter.drawRoundedRect( rect, 6, 6 );
1389 }
1390 else
1391 {
1392 // don't use rounded rect for small previews
1393 painter.drawRect( rect );
1394 }
1395 painter.end();
1396 return pixmap;
1397}
1398
1399QString QgsTextFormat::asCSS( double pointToPixelMultiplier ) const
1400{
1401 QString css;
1402
1403 switch ( lineHeightUnit() )
1404 {
1406 css += u"line-height: %1%;"_s.arg( lineHeight() * 100 );
1407 break;
1409 css += u"line-height: %1px;"_s.arg( lineHeight() );
1410 break;
1412 // While the Qt documentation states pt unit type is supported, it's ignored, convert to px
1413 css += u"line-height: %1px;"_s.arg( lineHeight() * pointToPixelMultiplier );
1414 break;
1416 // While the Qt documentation states cm unit type is supported, it's ignored, convert to px
1417 css += u"line-height: %1px;"_s.arg( lineHeight() * 2.83464567 * pointToPixelMultiplier );
1418 break;
1423 break;
1424 }
1425 css += u"color: rgba(%1,%2,%3,%4);"_s.arg( color().red() ).arg( color().green() ).arg( color().blue() ).arg( QString::number( color().alphaF(), 'f', 4 ) );
1426 QFont f = toQFont();
1428 {
1429 f.setPointSizeF( size() / 0.352778 );
1430 }
1431 css += QgsFontUtils::asCSS( toQFont(), pointToPixelMultiplier );
1432
1433 return css;
1434}
1435
1437 : mPosition( position )
1438{}
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
Definition qgis.h:2762
@ Rectangle
Text within rectangle layout mode.
Definition qgis.h:2960
BlendMode
Blending modes defining the available composition modes that can be used when painting.
Definition qgis.h:5037
@ Normal
Normal.
Definition qgis.h:5038
Capitalization
String capitalization options.
Definition qgis.h:3460
@ AllSmallCaps
Force all characters to small caps.
Definition qgis.h:3468
@ MixedCase
Mixed case, ie no change.
Definition qgis.h:3461
@ SmallCaps
Mixed case small caps.
Definition qgis.h:3465
TextOrientation
Text orientations.
Definition qgis.h:2944
RenderUnit
Rendering size units.
Definition qgis.h:5290
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5294
@ Millimeters
Millimeters.
Definition qgis.h:5291
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5295
@ Unknown
Mixed or unknown units.
Definition qgis.h:5297
@ MapUnits
Map units.
Definition qgis.h:5292
@ Pixels
Pixels.
Definition qgis.h:5293
@ Inches
Inches.
Definition qgis.h:5296
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5298
@ Antialiasing
Use antialiasing while drawing.
Definition qgis.h:2814
@ Center
Center align.
Definition qgis.h:3002
static QgsFontManager * fontManager()
Returns the application font manager, which manages available fonts and font installation for the QGI...
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
QString processFontFamilyName(const QString &name) const
Processes a font family name, applying any matching fontFamilyReplacements() to the name.
static QString asCSS(const QFont &font, double pointToPixelMultiplier=1.0)
Returns a CSS string representing the specified font as closely as possible.
static QString translateNamedStyle(const QString &namedStyle)
Returns the localized named style of a font, if such a translation is available.
static QString untranslateNamedStyle(const QString &namedStyle)
Returns the english named style of a font, if possible.
static QFont createFont(const QString &family, int pointSize=-1, int weight=-1, bool italic=false)
Creates a font with the specified family.
static bool fontFamilyMatchOnSystem(const QString &family, QString *chosen=nullptr, bool *match=nullptr)
Check whether font family is on system.
static bool fontFamilyOnSystem(const QString &family)
Check whether font family is on system in a quick manner, which does not compare [foundry].
static bool updateFontViaStyle(QFont &f, const QString &fontstyle, bool fallback=false)
Updates font with named style and retain all font properties.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
Perform transforms between map coordinates and device coordinates.
void setParameters(double mapUnitsPerPixel, double centerX, double centerY, int widthPixels, int heightPixels, double rotation)
Sets parameters for use in transforming coordinates.
Struct for storing maximum and minimum scales for measurements in map units.
static Qgis::BlendMode getBlendModeEnum(QPainter::CompositionMode blendMode)
Returns a Qgis::BlendMode corresponding to a QPainter::CompositionMode.
static QPainter::CompositionMode getCompositionMode(Qgis::BlendMode blendMode)
Returns a QPainter::CompositionMode corresponding to a Qgis::BlendMode.
@ FontStretchFactor
Font stretch factor, since QGIS 3.24.
@ TabStopDistance
Tab stop distance, since QGIS 3.38.
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the labeling property definitions.
A grouped map of multiple QgsProperty objects, each referenced by an integer key value.
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext(), bool ignoreContext=false) const final
Returns the set of any fields referenced by the active properties from the collection.
A container for the context for various read/write operations on objects.
void pushMessage(const QString &message, Qgis::MessageLevel level=Qgis::MessageLevel::Warning) const
Append a message to the context.
Contains information about the context of a rendering operation.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
void setDevicePixelRatio(float ratio)
Sets the device pixel ratio.
double convertToPainterUnits(double size, Qgis::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.
void setRasterizedRenderingPolicy(Qgis::RasterizedRenderingPolicy policy)
Sets the policy controlling when rasterisation of content during renders is permitted.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
Stores properties relating to a screen.
double devicePixelRatio() const
Returns the ratio between physical pixels and device-independent pixels for the screen.
bool isValid() const
Returns true if the properties are valid.
void updateRenderContextForScreen(QgsRenderContext &context) const
Updates the settings in a render context to match the screen settings.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QPainter::CompositionMode decodeBlendMode(const QString &s)
static QString encodeColor(const QColor &color)
Container for settings relating to a text background object.
QSizeF size() const
Returns the size of the background shape.
bool enabled() const
Returns whether the background is enabled.
SizeType sizeType() const
Returns the method used to determine the size of the background shape (e.g., fixed size or buffer aro...
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the shape size.
Qgis::RenderUnit sizeUnit() const
Returns the units used for the shape's size.
Container for settings relating to a text buffer.
Qgis::RenderUnit sizeUnit() const
Returns the units for the buffer size.
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.
Defines a tab position for a text format.
double position() const
Returns the tab position.
Tab(double position)
Constructor for a Tab at the specified position.
QgsTextFormat()
Default constructor for QgsTextFormat.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the text.
void setSize(double size)
Sets the size for rendered text.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
void setCapitalization(Qgis::Capitalization capitalization)
Sets the text capitalization style.
void setDataDefinedProperties(const QgsPropertyCollection &collection)
Sets the format's property collection, used for data defined overrides.
QStringList families() const
Returns the list of font families to use when restoring the text format, in order of precedence.
void setOrientation(Qgis::TextOrientation orientation)
Sets the orientation for the text.
void setFont(const QFont &font)
Sets the font used for rendering text.
QSet< QString > referencedFields(const QgsRenderContext &context) const
Returns all field names referenced by the configuration (e.g.
QList< QgsTextFormat::Tab > tabPositions() const
Returns the list of tab positions for tab stops.
void setFamilies(const QStringList &families)
Sets a list of font families to use for the text format, in order of precedence.
void setForcedItalic(bool forced)
Sets whether the format is set to force an italic style.
static QgsTextFormat fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QgsTextFormat.
double lineHeight() const
Returns the line height for text.
double tabStopDistance() const
Returns the distance for tab stops.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units for the size of rendered text.
int stretchFactor() const
Returns the text's stretch factor.
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.
void setStretchFactor(int factor)
Sets the text's stretch factor.
void setShadow(const QgsTextShadowSettings &shadowSettings)
Sets the text's drop shadow settings.
void setMask(const QgsTextMaskSettings &maskSettings)
Sets the text's masking settings.
void setPreviewBackgroundColor(const QColor &color)
Sets the background color that text will be rendered on for previews.
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...
void setTabStopDistanceUnit(Qgis::RenderUnit unit)
Sets the unit used for the tab stop distance.
void multiplyOpacity(double opacityFactor)
Multiply opacity by opacityFactor.
void setOpacity(double opacity)
Sets the text's opacity.
QPainter::CompositionMode blendMode() const
Returns the blending mode used for drawing the text.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
bool operator==(const QgsTextFormat &other) const
void setAllowHtmlFormatting(bool allow)
Sets whether text should be treated as a HTML document and HTML tags should be used for formatting th...
void setLineHeightUnit(Qgis::RenderUnit unit)
Sets the unit for the line height for text.
Qgis::RenderUnit lineHeightUnit() const
Returns the units for the line height for text.
Qgis::Capitalization capitalization() const
Returns the text capitalization style.
bool forcedItalic() const
Returns true if the format is set to force an italic style.
QgsTextMaskSettings & mask()
Returns a reference to the masking settings.
void setForcedBold(bool forced)
Sets whether the format is set to force a bold style.
bool isValid() const
Returns true if the format is valid.
void setTabStopDistanceMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the tab stop distance.
bool hasNonDefaultCompositionMode() const
Returns true if any component of the font format requires a non-default composition mode.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
QgsTextBackgroundSettings & background()
Returns a reference to the text background settings.
Qgis::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
static QgsTextFormat fromQFont(const QFont &font)
Returns a text format matching the settings from an input font.
void setValid()
Sets the format to a valid state, without changing any of the default format settings.
bool allowHtmlFormatting() const
Returns true if text should be treated as a HTML document and HTML tags should be used for formatting...
QFont toQFont() const
Returns a QFont matching the relevant settings from this text format.
bool operator!=(const QgsTextFormat &other) const
double opacity() const
Returns the text's opacity.
void setTabStopDistance(double distance)
Sets the distance for tab stops.
Qgis::TextOrientation orientation() const
Returns the orientation of the text.
QString namedStyle() const
Returns the named style for the font used for rendering text (e.g., "bold").
double size() const
Returns the size for rendered text.
QgsTextShadowSettings & shadow()
Returns a reference to the text drop shadow settings.
QgsTextFormat & operator=(const QgsTextFormat &other)
void setTabPositions(const QList< QgsTextFormat::Tab > &positions)
Sets the list of tab positions for tab stops.
static QPixmap textFormatPreviewPixmap(const QgsTextFormat &format, QSize size, const QString &previewText=QString(), int padding=0, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for a text format.
Qgis::RenderUnit tabStopDistanceUnit() const
Returns the units for the tab stop distance.
bool forcedBold() const
Returns true if the format is set to force a bold style.
void setBackground(const QgsTextBackgroundSettings &backgroundSettings)
Sets the text's background settings.q.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QString asCSS(double pointToPixelMultiplier=1.0) const
Returns a CSS string representing the specified text format as closely as possible.
QMimeData * toMimeData() const
Returns new mime data representing the text format settings.
void setSizeMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the size.
void setNamedStyle(const QString &style)
Sets the named style for the font used for rendering text.
QColor color() const
Returns the color that text will be rendered in.
QFont font() const
Returns the font used for rendering text.
void readFromLayer(QgsVectorLayer *layer)
Reads settings from a layer's custom properties (for QGIS 2.x projects).
QgsMapUnitScale tabStopDistanceMapUnitScale() const
Returns the map unit scale object for the tab stop distance.
QColor previewBackgroundColor() const
Returns the background color for text previews.
bool containsAdvancedEffects() const
Returns true if any component of the font format requires advanced effects such as blend modes,...
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
void setLineHeight(double height)
Sets the line height for text.
Container for settings relating to a selective masking around a text.
static Qgis::TextOrientation decodeTextOrientation(const QString &name, bool *ok=nullptr)
Attempts to decode a string representation of a text orientation.
static QColor readColor(QgsVectorLayer *layer, const QString &property, const QColor &defaultColor=Qt::black, bool withAlpha=true)
Converts an encoded color value from a layer property.
static QString encodeTextOrientation(Qgis::TextOrientation orientation)
Encodes a text orientation.
static int sizeToPixel(double size, const QgsRenderContext &c, Qgis::RenderUnit unit, const QgsMapUnitScale &mapUnitScale=QgsMapUnitScale())
Calculates pixel size (considering output size should be in pixel or map units, scale factors and opt...
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 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.
Container for settings relating to a text shadow.
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6935