QGIS API Documentation  2.11.0-Master
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 "qgsapplication.h"
19 #include "qgslogger.h"
20 
21 #include <QApplication>
22 #include <QFile>
23 #include <QFont>
24 #include <QFontDatabase>
25 #include <QFontInfo>
26 #include <QStringList>
27 
28 
30 {
31  QFontInfo fi = QFontInfo( f );
32  return fi.exactMatch();
33 }
34 
36 {
37  QFont tmpFont = QFont( family );
38  // compare just beginning of family string in case 'family [foundry]' differs
39  return tmpFont.family().startsWith( family, Qt::CaseInsensitive );
40 }
41 
42 bool QgsFontUtils::fontFamilyHasStyle( const QString& family, const QString& style )
43 {
44  QFontDatabase fontDB;
45  return ( fontFamilyOnSystem( family ) && fontDB.styles( family ).contains( style ) );
46 }
47 
48 bool QgsFontUtils::fontFamilyMatchOnSystem( const QString& family, QString* chosen, bool* match )
49 {
50  QFontDatabase fontDB;
51  QStringList fontFamilies = fontDB.families();
52  bool found = false;
53 
54  QList<QString>::const_iterator it = fontFamilies.constBegin();
55  for ( ; it != fontFamilies.constEnd(); ++it )
56  {
57  // first compare just beginning of 'family [foundry]' string
58  if ( it->startsWith( family, Qt::CaseInsensitive ) )
59  {
60  found = true;
61  // keep looking if match info is requested
62  if ( match )
63  {
64  // full 'family [foundry]' strings have to match
65  *match = ( *it == family );
66  if ( *match )
67  break;
68  }
69  else
70  {
71  break;
72  }
73  }
74  }
75 
76  if ( found )
77  {
78  if ( chosen )
79  {
80  // retrieve the family actually assigned by matching algorithm
81  QFont f = QFont( family );
82  *chosen = f.family();
83  }
84  }
85  else
86  {
87  if ( chosen )
88  {
89  *chosen = QString();
90  }
91 
92  if ( match )
93  {
94  *match = false;
95  }
96  }
97 
98  return found;
99 }
100 
101 bool QgsFontUtils::updateFontViaStyle( QFont& f, const QString& fontstyle, bool fallback )
102 {
103  if ( fontstyle.isEmpty() )
104  {
105  return false;
106  }
107 
108  QFontDatabase fontDB;
109 
110  if ( !fallback )
111  {
112  // does the font even have the requested style?
113  bool hasstyle = fontFamilyHasStyle( f.family(), fontstyle );
114  if ( !hasstyle )
115  {
116  return false;
117  }
118  }
119 
120  // is the font's style already the same as requested?
121  if ( fontstyle == fontDB.styleString( f ) )
122  {
123  return false;
124  }
125 
126  QFont appfont = QApplication::font();
127  int defaultSize = appfont.pointSize(); // QFontDatabase::font() needs an integer for size
128 
129  QFont styledfont;
130  bool foundmatch = false;
131 
132  // if fontDB.font() fails, it returns the default app font; but, that may be the target style
133  styledfont = fontDB.font( f.family(), fontstyle, defaultSize );
134  if ( appfont != styledfont || fontstyle != fontDB.styleString( f ) )
135  {
136  foundmatch = true;
137  }
138 
139  // default to first found style if requested style is unavailable
140  // this helps in the situations where the passed-in font has to have a named style applied
141  if ( fallback && !foundmatch )
142  {
143  QFont testFont = QFont( f );
144  testFont.setPointSize( defaultSize );
145 
146  // prefer a style that mostly matches the passed-in font
147  foreach ( const QString &style, fontDB.styles( f.family() ) )
148  {
149  styledfont = fontDB.font( f.family(), style, defaultSize );
150  styledfont = styledfont.resolve( f );
151  if ( testFont.toString() == styledfont.toString() )
152  {
153  foundmatch = true;
154  break;
155  }
156  }
157 
158  // fallback to first style found that works
159  if ( !foundmatch )
160  {
161  foreach ( const QString &style, fontDB.styles( f.family() ) )
162  {
163  styledfont = fontDB.font( f.family(), style, defaultSize );
164  if ( QApplication::font() != styledfont )
165  {
166  foundmatch = true;
167  break;
168  }
169  }
170  }
171  }
172 
173  // similar to QFont::resolve, but font may already have pixel size set
174  // and we want to make sure that's preserved
175  if ( foundmatch )
176  {
177  if ( f.pointSizeF() != -1 )
178  {
179  styledfont.setPointSizeF( f.pointSizeF() );
180  }
181  else if ( f.pixelSize() != -1 )
182  {
183  styledfont.setPixelSize( f.pixelSize() );
184  }
185  styledfont.setCapitalization( f.capitalization() );
186  styledfont.setUnderline( f.underline() );
187  styledfont.setStrikeOut( f.strikeOut() );
188  styledfont.setWordSpacing( f.wordSpacing() );
189  styledfont.setLetterSpacing( QFont::AbsoluteSpacing, f.letterSpacing() );
190  f = styledfont;
191 
192  return true;
193  }
194 
195  return false;
196 }
197 
199 {
200  return "QGIS Vera Sans";
201 }
202 
204 {
205  // load standard test font from filesystem or testdata.qrc (for unit tests and general testing)
206  bool fontsLoaded = false;
207 
208  QString fontFamily = standardTestFontFamily();
209  QMap<QString, QString> fontStyles;
210  fontStyles.insert( "Roman", "QGIS-Vera/QGIS-Vera.ttf" );
211  fontStyles.insert( "Oblique", "QGIS-Vera/QGIS-VeraIt.ttf" );
212  fontStyles.insert( "Bold", "QGIS-Vera/QGIS-VeraBd.ttf" );
213  fontStyles.insert( "Bold Oblique", "QGIS-Vera/QGIS-VeraBI.ttf" );
214 
216  for ( ; f != fontStyles.constEnd(); ++f )
217  {
218  QString fontstyle( f.key() );
219  QString fontpath( f.value() );
220  if ( !( loadstyles.contains( fontstyle ) || loadstyles.contains( "All" ) ) )
221  {
222  continue;
223  }
224  QString familyStyle = QString( "%1 %2" ).arg( fontFamily ).arg( fontstyle );
225 
226  if ( fontFamilyHasStyle( fontFamily, fontstyle ) )
227  {
228  fontsLoaded = ( fontsLoaded || false );
229  QgsDebugMsg( QString( "Test font '%1' already available" ).arg( familyStyle ) );
230  }
231  else
232  {
233  bool loaded = false;
235  {
236  // workaround for bugs with Qt 4.8.5 (other versions?) on Mac 10.9, where fonts
237  // from qrc resources load but fail to work and default font is substituted [LS]:
238  // https://bugreports.qt-project.org/browse/QTBUG-30917
239  // https://bugreports.qt-project.org/browse/QTBUG-32789
240  QString fontPath( QgsApplication::buildSourcePath() + "/tests/testdata/font/" + fontpath );
241  int fontID = QFontDatabase::addApplicationFont( fontPath );
242  loaded = ( fontID != -1 );
243  fontsLoaded = ( fontsLoaded || loaded );
244  QgsDebugMsg( QString( "Test font '%1' %2 from filesystem [%3]" )
245  .arg( familyStyle ).arg( loaded ? "loaded" : "FAILED to load" ).arg( fontPath ) );
246  }
247  else
248  {
249  QFile fontResource( ":/testdata/font/" + fontpath );
250  if ( fontResource.open( QIODevice::ReadOnly ) )
251  {
252  int fontID = QFontDatabase::addApplicationFontFromData( fontResource.readAll() );
253  loaded = ( fontID != -1 );
254  fontsLoaded = ( fontsLoaded || loaded );
255  }
256  QgsDebugMsg( QString( "Test font '%1' %2 from testdata.qrc" )
257  .arg( familyStyle ).arg( loaded ? "loaded" : "FAILED to load" ) );
258  }
259  }
260  }
261 
262  return fontsLoaded;
263 }
264 
265 QFont QgsFontUtils::getStandardTestFont( const QString& style, int pointsize )
266 {
267  QFontDatabase fontDB;
268  if ( ! fontFamilyHasStyle( standardTestFontFamily(), style ) )
269  {
270  loadStandardTestFonts( QStringList() << style );
271  }
272 
273  QFont f = fontDB.font( standardTestFontFamily(), style, pointsize );
274  // in case above statement fails to set style
275  f.setBold( style.contains( "Bold" ) );
276  f.setItalic( style.contains( "Oblique" ) );
277 
278  return f;
279 }
280 
281 QDomElement QgsFontUtils::toXmlElement( const QFont& font, QDomDocument& document, const QString& elementName )
282 {
283  QDomElement fontElem = document.createElement( elementName );
284  fontElem.setAttribute( "description", font.toString() );
285  fontElem.setAttribute( "style", untranslateNamedStyle( font.styleName() ) );
286  return fontElem;
287 }
288 
289 bool QgsFontUtils::setFromXmlElement( QFont& font, const QDomElement& element )
290 {
291  if ( element.isNull() )
292  {
293  return false;
294  }
295 
296  font.fromString( element.attribute( "description" ) );
297  if ( element.hasAttribute( "style" ) )
298  {
299  ( void )updateFontViaStyle( font, translateNamedStyle( element.attribute( "style" ) ) );
300  }
301 
302  return true;
303 }
304 
305 bool QgsFontUtils::setFromXmlChildNode( QFont& font, const QDomElement& element, const QString& childNode )
306 {
307  if ( element.isNull() )
308  {
309  return false;
310  }
311 
312  QDomNodeList nodeList = element.elementsByTagName( childNode );
313  if ( nodeList.size() > 0 )
314  {
315  QDomElement fontElem = nodeList.at( 0 ).toElement();
316  return setFromXmlElement( font, fontElem );
317  }
318  else
319  {
320  return false;
321  }
322 }
323 
325 {
326  QMap<QString, QString> translatedStyleMap;
327  QStringList words = QStringList() << "Normal" << "Light" << "Bold" << "Black" << "Demi" << "Italic" << "Oblique";
328  foreach ( const QString& word, words )
329  {
330  translatedStyleMap.insert( QCoreApplication::translate( "QFontDatabase", qPrintable( word ) ), word );
331  }
332  return translatedStyleMap;
333 }
334 
336 {
337  QStringList words = namedStyle.split( " ", QString::SkipEmptyParts );
338  for ( int i = 0, n = words.length(); i < n; ++i )
339  {
340  words[i] = QCoreApplication::translate( "QFontDatabase", words[i].toUtf8(), 0, QCoreApplication::UnicodeUTF8 );
341  }
342  return words.join( " " );
343 }
344 
346 {
347  static QMap<QString, QString> translatedStyleMap = createTranslatedStyleMap();
348  QStringList words = namedStyle.split( " ", QString::SkipEmptyParts );
349  for ( int i = 0, n = words.length(); i < n; ++i )
350  {
351  if ( translatedStyleMap.contains( words[i] ) )
352  {
353  words[i] = translatedStyleMap.value( words[i] );
354  }
355  else
356  {
357  QgsDebugMsg( QString( "Warning: style map does not contain %1" ).arg( words[i] ) );
358  }
359  }
360  return words.join( " " );
361 }
QDomNodeList elementsByTagName(const QString &tagname) const
void setPointSize(int pointSize)
int addApplicationFontFromData(const QByteArray &fontData)
bool contains(const Key &key) const
int pixelSize() const
QString attribute(const QString &name, const QString &defValue) const
int length() const
qreal pointSizeF() const
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
const_iterator constBegin() const
static bool setFromXmlElement(QFont &font, const QDomElement &element)
Sets the properties of a font to match the properties stored in an XML element.
bool contains(const QString &str, Qt::CaseSensitivity cs) const
void setUnderline(bool enable)
static bool fontFamilyHasStyle(const QString &family, const QString &style)
Check whether font family on system has specific style.
Capitalization capitalization() const
QString join(const QString &separator) const
static bool isRunningFromBuildDir()
Indicates whether running from build directory (not installed)
static QString translateNamedStyle(const QString &namedStyle)
Returns the localized named style of a font, if such a translation is available.
QStringList styles(const QString &family) const
QString styleName() const
QDomElement toElement() const
void setBold(bool enable)
void setPixelSize(int pixelSize)
QFont font()
bool fromString(const QString &descrip)
bool hasAttribute(const QString &name) const
static bool fontFamilyOnSystem(const QString &family)
Check whether font family is on system in a quick manner, which does not compare [foundry].
qreal letterSpacing() const
void setAttribute(const QString &name, const QString &value)
int addApplicationFont(const QString &fileName)
static QFont getStandardTestFont(const QString &style="Roman", int pointsize=12)
Get standard test font with specific style.
bool isEmpty() const
const_iterator constEnd() const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
QByteArray readAll()
QFont resolve(const QFont &other) const
static QMap< QString, QString > createTranslatedStyleMap()
bool underline() const
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 QString standardTestFontFamily()
Get standard test font family.
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
QString styleString(const QFont &font)
void setWordSpacing(qreal spacing)
bool startsWith(const T &value) const
bool contains(QChar ch, Qt::CaseSensitivity cs) const
bool exactMatch() const
void setItalic(bool enable)
void setPointSizeF(qreal pointSize)
bool isNull() const
const Key key(const T &value) const
static QString untranslateNamedStyle(const QString &namedStyle)
Returns the english named style of a font, if possible.
static QString buildSourcePath()
Returns path to the source directory.
static bool loadStandardTestFonts(QStringList loadstyles)
Loads standard test fonts from filesystem or qrc resource.
QString toString() const
QStringList families(WritingSystem writingSystem) const
QString family() const
static bool updateFontViaStyle(QFont &f, const QString &fontstyle, bool fallback=false)
Updates font with named style and retain all font properties.
QString translate(const char *context, const char *sourceText, const char *disambiguation, Encoding encoding)
void setStrikeOut(bool enable)
void setCapitalization(Capitalization caps)
iterator insert(const Key &key, const T &value)
static bool fontMatchOnSystem(const QFont &f)
Check whether exact font is on system.
static QDomElement toXmlElement(const QFont &font, QDomDocument &document, const QString &elementName)
Returns a DOM element containing the properties of the font.
int size() const
const_iterator constEnd() const
QDomElement createElement(const QString &tagName)
bool strikeOut() const
const_iterator constBegin() const
QFont font(const QString &family, const QString &style, int pointSize) const
void setLetterSpacing(SpacingType type, qreal spacing)
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
static bool fontFamilyMatchOnSystem(const QString &family, QString *chosen=0, bool *match=0)
Check whether font family is on system.
int pointSize() const
QDomNode at(int index) const
qreal wordSpacing() const
const T value(const Key &key) const