QGIS API Documentation 4.1.0-Master (64dc32379c2)
Loading...
Searching...
No Matches
qgsfontutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsfontutils.h
3 ---------------------
4 begin : June 5, 2013
5 copyright : (C) 2013 by Larry Shaffer
6 email : larrys at dakotacarto 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 "qgsfontutils.h"
17
18#include <memory>
19
20#include "qgis.h"
21#include "qgsapplication.h"
22#include "qgslogger.h"
24#include "qgssettingstree.h"
25
26#include <QApplication>
27#include <QFile>
28#include <QFont>
29#include <QFontDatabase>
30#include <QFontInfo>
31#include <QMimeData>
32#include <QString>
33#include <QStringList>
34
35using namespace Qt::StringLiterals;
36
38 = new QgsSettingsEntryStringList( u"recent"_s, QgsSettingsTree::sTreeFonts, QStringList(), u"List of recently used font families."_s );
39
40bool QgsFontUtils::fontMatchOnSystem( const QFont &f )
41{
42 const QFontInfo fi = QFontInfo( f );
43 return fi.exactMatch();
44}
45
46bool QgsFontUtils::fontFamilyOnSystem( const QString &family )
47{
48 const QFont tmpFont = QFont( family );
49 // compare just beginning of family string in case 'family [foundry]' differs
50 return tmpFont.family().startsWith( family, Qt::CaseInsensitive );
51}
52
53bool QgsFontUtils::fontFamilyHasStyle( const QString &family, const QString &style )
54{
55 const QFontDatabase fontDB;
56 if ( !fontFamilyOnSystem( family ) )
57 return false;
58
59 if ( fontDB.styles( family ).contains( style ) )
60 return true;
61
62 QString modified( style );
63#ifdef Q_OS_WIN
64 if ( style == "Roman" )
65 modified = "Normal";
66 else if ( style == "Oblique" )
67 modified = "Italic";
68 else if ( style == "Bold Oblique" )
69 modified = "Bold Italic";
70 if ( fontDB.styles( family ).contains( modified ) )
71 return true;
72#endif
73
74 // Deal with Qt6 Normal -> Regular style renaming
75 if ( modified == "Normal" )
76 modified = "Regular";
77 else if ( modified == QCoreApplication::translate( "QFontDatabase", "Normal" ) )
78 modified = QCoreApplication::translate( "QFontDatabase", "Regular" );
79 if ( fontDB.styles( family ).contains( modified ) )
80 return true;
81
82 return false;
83}
84
85QString QgsFontUtils::resolveFontStyleName( const QFont &font )
86{
87 auto styleNameIsMatch = [&font]( const QString &candidate ) -> bool {
88 // confirm that style name matches bold/italic flags
89 QFont testFont( font.family() );
90 testFont.setStyleName( candidate );
91 return testFont.italic() == font.italic() && testFont.weight() == font.weight();
92 };
93
94 // attempt 1
95 const QFontInfo fontInfo( font );
96 QString styleName = fontInfo.styleName();
97 if ( !styleName.isEmpty() )
98 {
99 if ( styleNameIsMatch( styleName ) )
100 return styleName;
101 }
102
103 // attempt 2
104 styleName = QFontDatabase().styleString( font );
105 if ( !styleName.isEmpty() )
106 {
107 if ( styleNameIsMatch( styleName ) )
108 return styleName;
109 }
110
111 // failed
112 return QString();
113}
114
115bool QgsFontUtils::fontFamilyMatchOnSystem( const QString &family, QString *chosen, bool *match )
116{
117 const QFontDatabase fontDB;
118 const QStringList fontFamilies = fontDB.families();
119 bool found = false;
120
121 QList<QString>::const_iterator it = fontFamilies.constBegin();
122 for ( ; it != fontFamilies.constEnd(); ++it )
123 {
124 // first compare just beginning of 'family [foundry]' string
125 if ( it->startsWith( family, Qt::CaseInsensitive ) )
126 {
127 found = true;
128 // keep looking if match info is requested
129 if ( match )
130 {
131 // full 'family [foundry]' strings have to match
132 *match = ( *it == family );
133 if ( *match )
134 break;
135 }
136 else
137 {
138 break;
139 }
140 }
141 }
142
143 if ( found )
144 {
145 if ( chosen )
146 {
147 // retrieve the family actually assigned by matching algorithm
148 const QFont f = QFont( family );
149 *chosen = f.family();
150 }
151 }
152 else
153 {
154 if ( chosen )
155 {
156 *chosen = QString();
157 }
158
159 if ( match )
160 {
161 *match = false;
162 }
163 }
164
165 return found;
166}
167
168bool QgsFontUtils::updateFontViaStyle( QFont &f, const QString &fontstyle, bool fallback )
169{
170 if ( fontstyle.isEmpty() )
171 {
172 return false;
173 }
174
175 QFontDatabase fontDB;
176 QString actualFontStyle = fontstyle;
177
178 if ( !fallback )
179 {
180 // does the font even have the requested style?
181 bool hasstyle = fontFamilyHasStyle( f.family(), actualFontStyle );
182 if ( !hasstyle )
183 {
184 actualFontStyle = untranslateNamedStyle( fontstyle );
185 hasstyle = fontFamilyHasStyle( f.family(), actualFontStyle );
186 }
187
188 if ( !hasstyle )
189 {
190 return false;
191 }
192 }
193
194 // is the font's style already the same as requested?
195 if ( actualFontStyle == fontDB.styleString( f ) )
196 {
197 if ( f.styleName().isEmpty() )
198 {
199 f.setStyleName( actualFontStyle );
200 return true;
201 }
202 return false;
203 }
204
205 const QFont appfont = QApplication::font();
206 const int defaultSize = appfont.pointSize(); // QFontDatabase::font() needs an integer for size
207
208 QFont styledfont;
209 bool foundmatch = false;
210
211 // if fontDB.font() fails, it returns the default app font; but, that may be the target style
212 styledfont = fontDB.font( f.family(), actualFontStyle, defaultSize );
213 if ( appfont != styledfont || actualFontStyle != fontDB.styleString( f ) )
214 {
215 foundmatch = true;
216 }
217
218 // default to first found style if requested style is unavailable
219 // this helps in the situations where the passed-in font has to have a named style applied
220 if ( fallback && !foundmatch )
221 {
222 QFont testFont = QFont( f );
223 testFont.setPointSize( defaultSize );
224
225 // prefer a style that mostly matches the passed-in font
226 const auto constFamily = fontDB.styles( f.family() );
227 for ( const QString &style : constFamily )
228 {
229 styledfont = fontDB.font( f.family(), style, defaultSize );
230 styledfont = styledfont.resolve( f );
231 if ( testFont.toString() == styledfont.toString() )
232 {
233 foundmatch = true;
234 break;
235 }
236 }
237
238 // fallback to first style found that works
239 if ( !foundmatch )
240 {
241 for ( const QString &style : constFamily )
242 {
243 styledfont = fontDB.font( f.family(), style, defaultSize );
244 if ( QApplication::font() != styledfont )
245 {
246 foundmatch = true;
247 break;
248 }
249 }
250 }
251 }
252
253 // similar to QFont::resolve, but font may already have pixel size set
254 // and we want to make sure that's preserved
255 if ( foundmatch )
256 {
257 if ( !qgsDoubleNear( f.pointSizeF(), -1 ) )
258 {
259 styledfont.setPointSizeF( f.pointSizeF() );
260 }
261 else if ( f.pixelSize() != -1 )
262 {
263 styledfont.setPixelSize( f.pixelSize() );
264 }
265 styledfont.setCapitalization( f.capitalization() );
266 styledfont.setUnderline( f.underline() );
267 styledfont.setStrikeOut( f.strikeOut() );
268 styledfont.setWordSpacing( f.wordSpacing() );
269 styledfont.setLetterSpacing( QFont::AbsoluteSpacing, f.letterSpacing() );
270 f = styledfont;
271
272 return true;
273 }
274
275 return false;
276}
277
279{
280 return u"QGIS Vera Sans"_s;
281}
282
283bool QgsFontUtils::loadStandardTestFonts( const QStringList &loadstyles )
284{
285 // load standard test font from filesystem or testdata.qrc (for unit tests and general testing)
286 bool fontsLoaded = false;
287
288 QMap<QString, QString> fontStyles;
289 fontStyles.insert( u"Roman"_s, u"QGIS-Vera/QGIS-Vera.ttf"_s );
290 fontStyles.insert( u"Oblique"_s, u"QGIS-Vera/QGIS-VeraIt.ttf"_s );
291 fontStyles.insert( u"Bold"_s, u"QGIS-Vera/QGIS-VeraBd.ttf"_s );
292 fontStyles.insert( u"Bold Oblique"_s, u"QGIS-Vera/QGIS-VeraBI.ttf"_s );
293 fontStyles.insert( u"Deja Bold"_s, u"QGIS-DejaVu/QGISDejaVuSans-Bold.ttf"_s );
294
295 QMap<QString, QString>::const_iterator f = fontStyles.constBegin();
296 for ( ; f != fontStyles.constEnd(); ++f )
297 {
298 const QString fontpath( f.value() );
299 if ( !( loadstyles.contains( f.key() ) || loadstyles.contains( u"All"_s ) ) )
300 {
301 continue;
302 }
303
304 const QString fontFamily = !f.key().startsWith( "Deja"_L1 ) ? standardTestFontFamily() : u"QGIS DejaVu Sans"_s;
305 const QString fontstyle = !f.key().startsWith( "Deja"_L1 ) ? f.key() : f.key().mid( 5 );
306
307 if ( fontFamilyHasStyle( fontFamily, fontstyle ) )
308 {
309 QgsDebugMsgLevel( u"Test font '%1 %2' already available"_s.arg( fontFamily, fontstyle ), 2 );
310 }
311 else
312 {
313 bool loaded = false;
315 {
316 // workaround for bugs with Qt 4.8.5 (other versions?) on Mac 10.9, where fonts
317 // from qrc resources load but fail to work and default font is substituted [LS]:
318 // https://bugreports.qt.io/browse/QTBUG-30917
319 // https://bugreports.qt.io/browse/QTBUG-32789
320 const QString fontPath( QgsApplication::buildSourcePath() + "/tests/testdata/font/" + fontpath );
321 const int fontID = QFontDatabase::addApplicationFont( fontPath );
322 loaded = ( fontID != -1 );
323 fontsLoaded = ( fontsLoaded || loaded );
324 QgsDebugMsgLevel( u"Test font '%1 %2' %3 from filesystem [%4]"_s.arg( fontFamily, fontstyle, loaded ? "loaded" : "FAILED to load", fontPath ), 2 );
325 QgsDebugMsgLevel( u"font families in %1: %2"_s.arg( fontID ).arg( QFontDatabase().applicationFontFamilies( fontID ).join( "," ) ), 2 );
326 }
327 else
328 {
329 QFile fontResource( ":/testdata/font/" + fontpath );
330 if ( fontResource.open( QIODevice::ReadOnly ) )
331 {
332 const int fontID = QFontDatabase::addApplicationFontFromData( fontResource.readAll() );
333 loaded = ( fontID != -1 );
334 fontsLoaded = ( fontsLoaded || loaded );
335 }
336 QgsDebugMsgLevel( u"Test font '%1' (%2) %3 from testdata.qrc"_s.arg( fontFamily, fontstyle, loaded ? "loaded" : "FAILED to load" ), 2 );
337 }
338 }
339 }
340
341 return fontsLoaded;
342}
343
344QFont QgsFontUtils::getStandardTestFont( const QString &style, int pointsize )
345{
346 const QString fontFamily = !style.startsWith( "Deja"_L1 ) ? standardTestFontFamily() : u"QGIS DejaVu Sans"_s;
347 const QString fontStyle = !style.startsWith( "Deja"_L1 ) ? style : style.mid( 5 );
348
349 if ( !fontFamilyHasStyle( fontFamily, fontStyle ) )
350 {
351 loadStandardTestFonts( QStringList() << style );
352 }
353
354 const QFontDatabase fontDB;
355 QFont f = fontDB.font( fontFamily, fontStyle, pointsize );
356#ifdef Q_OS_WIN
357 if ( !f.exactMatch() )
358 {
359 QString modified;
360 if ( fontStyle == "Roman" )
361 modified = "Normal";
362 else if ( fontStyle == "Oblique" )
363 modified = "Italic";
364 else if ( fontStyle == "Bold Oblique" )
365 modified = "Bold Italic";
366 if ( !modified.isEmpty() )
367 f = fontDB.font( fontFamily, modified, pointsize );
368 }
369 if ( !f.exactMatch() )
370 {
371 QgsDebugMsgLevel( u"Inexact font match - consider installing the %1 font."_s.arg( fontFamily ), 2 );
372 QgsDebugMsgLevel( u"Requested: %1"_s.arg( f.toString() ), 2 );
373 QFontInfo fi( f );
375 u"Replaced: %1,%2,%3,%4,%5,%6,%7,%8,%9"_s.arg( fi.family() )
376 .arg( fi.pointSizeF() )
377 .arg( fi.pixelSize() )
378 .arg( fi.styleHint() )
379 .arg( fi.weight() )
380 .arg( fi.style() )
381 .arg( fi.underline() )
382 .arg( fi.strikeOut() )
383 .arg( fi.fixedPitch() ),
384 2
385 );
386 }
387#endif
388 // in case above statement fails to set style
389 f.setBold( fontStyle.contains( "Bold"_L1 ) );
390 f.setItalic( fontStyle.contains( "Oblique"_L1 ) || fontStyle.contains( "Italic"_L1 ) );
391
392 return f;
393}
394
395QDomElement QgsFontUtils::toXmlElement( const QFont &font, QDomDocument &document, const QString &elementName )
396{
397 QDomElement fontElem = document.createElement( elementName );
398 fontElem.setAttribute( u"description"_s, font.toString() );
399 fontElem.setAttribute( u"style"_s, untranslateNamedStyle( font.styleName() ) );
400 fontElem.setAttribute( u"bold"_s, font.bold() ? QChar( '1' ) : QChar( '0' ) );
401 fontElem.setAttribute( u"italic"_s, font.italic() ? QChar( '1' ) : QChar( '0' ) );
402 fontElem.setAttribute( u"underline"_s, font.underline() ? QChar( '1' ) : QChar( '0' ) );
403 fontElem.setAttribute( u"strikethrough"_s, font.strikeOut() ? QChar( '1' ) : QChar( '0' ) );
404 return fontElem;
405}
406
407bool QgsFontUtils::setFromXmlElement( QFont &font, const QDomElement &element )
408{
409 if ( element.isNull() )
410 {
411 return false;
412 }
413
414 font.fromString( element.attribute( u"description"_s ) );
415
416 if ( element.hasAttribute( u"bold"_s ) && element.attribute( u"bold"_s ) == QChar( '1' ) )
417 {
418 font.setBold( true );
419 }
420 if ( element.hasAttribute( u"italic"_s ) )
421 {
422 font.setItalic( element.attribute( u"italic"_s ) == QChar( '1' ) );
423 }
424 if ( element.hasAttribute( u"underline"_s ) )
425 {
426 font.setUnderline( element.attribute( u"underline"_s ) == QChar( '1' ) );
427 }
428 if ( element.hasAttribute( u"strikethrough"_s ) )
429 {
430 font.setStrikeOut( element.attribute( u"strikethrough"_s ) == QChar( '1' ) );
431 }
432
433 if ( element.hasAttribute( u"style"_s ) )
434 {
435 ( void ) updateFontViaStyle( font, translateNamedStyle( element.attribute( u"style"_s ) ) );
436 }
437
438 return true;
439}
440
441bool QgsFontUtils::setFromXmlChildNode( QFont &font, const QDomElement &element, const QString &childNode )
442{
443 if ( element.isNull() )
444 {
445 return false;
446 }
447
448 const QDomNodeList nodeList = element.elementsByTagName( childNode );
449 if ( !nodeList.isEmpty() )
450 {
451 const QDomElement fontElem = nodeList.at( 0 ).toElement();
452 return setFromXmlElement( font, fontElem );
453 }
454 else
455 {
456 return false;
457 }
458}
459
460QMimeData *QgsFontUtils::toMimeData( const QFont &font )
461{
462 std::unique_ptr< QMimeData > mimeData( new QMimeData );
463
464 QDomDocument fontDoc;
465 const QDomElement fontElem = toXmlElement( font, fontDoc, u"font"_s );
466 fontDoc.appendChild( fontElem );
467 mimeData->setText( fontDoc.toString() );
468
469 return mimeData.release();
470}
471
472QFont QgsFontUtils::fromMimeData( const QMimeData *data, bool *ok )
473{
474 QFont font;
475 if ( ok )
476 *ok = false;
477
478 if ( !data )
479 return font;
480
481 const QString text = data->text();
482 if ( !text.isEmpty() )
483 {
484 QDomDocument doc;
485 QDomElement elem;
486
487 if ( doc.setContent( text ) )
488 {
489 elem = doc.documentElement();
490
491 if ( elem.nodeName() != "font"_L1 )
492 elem = elem.firstChildElement( u"font"_s );
493
494 if ( setFromXmlElement( font, elem ) )
495 {
496 if ( ok )
497 *ok = true;
498 }
499 return font;
500 }
501 }
502 return font;
503}
504
505static QMap<QString, QString> createTranslatedStyleMap()
506{
507 QMap<QString, QString> translatedStyleMap;
508 const QStringList words = QStringList() << u"Normal"_s << u"Regular"_s << u"Light"_s << u"Bold"_s << u"Black"_s << u"Demi"_s << u"Italic"_s << u"Oblique"_s;
509 const auto constWords = words;
510 for ( const QString &word : constWords )
511 {
512 translatedStyleMap.insert( QCoreApplication::translate( "QFontDatabase", qPrintable( word ) ), word );
513 }
514 return translatedStyleMap;
515}
516
517QString QgsFontUtils::translateNamedStyle( const QString &namedStyle )
518{
519 QStringList words = namedStyle.split( ' ', Qt::SkipEmptyParts );
520 for ( int i = 0, n = words.length(); i < n; ++i )
521 {
522 words[i] = QCoreApplication::translate( "QFontDatabase", words[i].toLocal8Bit().constData() );
523 }
524 return words.join( ' '_L1 );
525}
526
527QString QgsFontUtils::untranslateNamedStyle( const QString &namedStyle )
528{
529 static const QMap<QString, QString> translatedStyleMap = createTranslatedStyleMap();
530 QStringList words = namedStyle.split( ' ', Qt::SkipEmptyParts );
531
532 for ( int i = 0, n = words.length(); i < n; ++i )
533 {
534 if ( translatedStyleMap.contains( words[i] ) )
535 {
536 words[i] = translatedStyleMap.value( words[i] );
537 }
538 else
539 {
540 QgsDebugMsgLevel( u"Warning: style map does not contain %1"_s.arg( words[i] ), 2 );
541 }
542 }
543 return words.join( ' '_L1 );
544}
545
546QString QgsFontUtils::asCSS( const QFont &font, double pointToPixelScale )
547{
548 QString css = u"font-family: "_s + font.family() + ';';
549
550 //style
551 css += "font-style: "_L1;
552 switch ( font.style() )
553 {
554 case QFont::StyleNormal:
555 css += "normal"_L1;
556 break;
557 case QFont::StyleItalic:
558 css += "italic"_L1;
559 break;
560 case QFont::StyleOblique:
561 css += "oblique"_L1;
562 break;
563 }
564 css += ';';
565
566 //weight
567 int cssWeight = 400;
568 switch ( font.weight() )
569 {
570 case QFont::Light:
571 cssWeight = 300;
572 break;
573 case QFont::Normal:
574 cssWeight = 400;
575 break;
576 case QFont::DemiBold:
577 cssWeight = 600;
578 break;
579 case QFont::Bold:
580 cssWeight = 700;
581 break;
582 case QFont::Black:
583 cssWeight = 900;
584 break;
585 case QFont::Thin:
586 cssWeight = 100;
587 break;
588 case QFont::ExtraLight:
589 cssWeight = 200;
590 break;
591 case QFont::Medium:
592 cssWeight = 500;
593 break;
594 case QFont::ExtraBold:
595 cssWeight = 800;
596 break;
597 }
598 css += u"font-weight: %1;"_s.arg( cssWeight );
599
600 //size
601 css += u"font-size: %1px;"_s.arg( font.pointSizeF() >= 0 ? font.pointSizeF() * pointToPixelScale : font.pixelSize() );
602
603 return css;
604}
605
606void QgsFontUtils::addRecentFontFamily( const QString &family )
607{
608 if ( family.isEmpty() )
609 {
610 return;
611 }
612
613 QStringList recentFamilies = settingsRecentFontFamilies->value();
614
615 //remove matching families
616 recentFamilies.removeAll( family );
617
618 //then add to start of list
619 recentFamilies.prepend( family );
620
621 //trim to 10 fonts
622 recentFamilies = recentFamilies.mid( 0, 10 );
623
624 settingsRecentFontFamilies->setValue( recentFamilies );
625}
626
628{
629 return settingsRecentFontFamilies->value();
630}
631
632void QgsFontUtils::setFontFamily( QFont &font, const QString &family )
633{
634 font.setFamily( family );
635 if ( !font.exactMatch() )
636 {
637 // some Qt versions struggle with fonts with certain unusual characters
638 // in their names, eg "ESRI Oil, Gas, & Water". Calling "setFamilies"
639 // can workaround these issues... (in some cases!)
640 font.setFamilies( { family } );
641 }
642}
643
644QFont QgsFontUtils::createFont( const QString &family, int pointSize, int weight, bool italic )
645{
646 QFont font( family, pointSize, weight, italic );
647 if ( !font.exactMatch() )
648 {
649 // some Qt versions struggle with fonts with certain unusual characters
650 // in their names, eg "ESRI Oil, Gas, & Water". Calling "setFamilies"
651 // can workaround these issues... (in some cases!)
652 font.setFamilies( { family } );
653 }
654 return font;
655}
static QString buildSourcePath()
Returns path to the source directory.
static bool isRunningFromBuildDir()
Indicates whether running from build directory (not installed).
static QString resolveFontStyleName(const QFont &font)
Attempts to resolve the style name corresponding to the specified font object.
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 bool setFromXmlElement(QFont &font, const QDomElement &element)
Sets the properties of a font to match the properties stored in an XML element.
static bool setFromXmlChildNode(QFont &font, const QDomElement &element, const QString &childNode)
Sets the properties of a font to match the properties stored in an XML child node.
static QFont createFont(const QString &family, int pointSize=-1, int weight=-1, bool italic=false)
Creates a font with the specified family.
static QMimeData * toMimeData(const QFont &font)
Returns new mime data representing the specified font settings.
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.
static bool fontMatchOnSystem(const QFont &f)
Check whether exact font is on system.
static bool loadStandardTestFonts(const QStringList &loadstyles)
Loads standard test fonts from filesystem or qrc resource.
static QFont getStandardTestFont(const QString &style="Roman", int pointsize=12)
Gets standard test font with specific style.
static void setFontFamily(QFont &font, const QString &family)
Sets the family for a font object.
static QDomElement toXmlElement(const QFont &font, QDomDocument &document, const QString &elementName)
Returns a DOM element containing the properties of the font.
static void addRecentFontFamily(const QString &family)
Adds a font family to the list of recently used font families.
static QString standardTestFontFamily()
Gets standard test font family.
static QFont fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QFont.
static bool fontFamilyHasStyle(const QString &family, const QString &style)
Check whether font family on system has specific style.
static const QgsSettingsEntryStringList * settingsRecentFontFamilies
static QStringList recentFontFamilies()
Returns a list of recently used font families.
A string list settings entry.
static QgsSettingsTreeNode * sTreeFonts
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7148
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63