QGIS API Documentation 4.3.0-Master (40e84817713)
Loading...
Searching...
No Matches
qgsfontmanager.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsfontmanager.cpp
3 ------------------
4 Date : June 2022
5 Copyright : (C) 2022 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 "qgsfontmanager.h"
17
18#include <nlohmann/json.hpp>
19
20#include "qgsapplication.h"
22#include "qgsfileutils.h"
23#include "qgsreadwritelocker.h"
26#include "qgssettingstree.h"
27#include "qgsziputils.h"
28
29#include <QDir>
30#include <QFontDatabase>
31#include <QRegularExpression>
32#include <QRegularExpressionMatch>
33#include <QString>
34#include <QTemporaryDir>
35#include <QTemporaryFile>
36
37#include "moc_qgsfontmanager.cpp"
38
39using namespace Qt::StringLiterals;
40
42 = new QgsSettingsEntryStringList( u"fontFamilyReplacements"_s, QgsSettingsTree::sTreeFonts, QStringList(), u"Automatic font family replacements"_s );
43
45 = new QgsSettingsEntryBool( u"downloadMissingFonts"_s, QgsSettingsTree::sTreeFonts, true, u"Automatically download missing fonts whenever possible"_s );
46
47//
48// QgsFontDownloadDetails
49//
50
52
53QgsFontDownloadDetails::QgsFontDownloadDetails( const QString &family, const QStringList &fontUrls, const QString &licenseUrl )
54 : mFamily( family )
55 , mStandardizedFamily( standardizeFamily( family ) )
56 , mFontUrls( fontUrls )
57 , mLicenseUrl( licenseUrl )
58{}
59
61{
62 const thread_local QRegularExpression charsToRemove( u"[^a-z]"_s );
63 const thread_local QRegularExpression styleNames( u"(?:normal|regular|light|bold|black|demi|italic|oblique|medium|thin)"_s );
64
65 QString processed = family.toLower();
66 processed.replace( styleNames, QString() );
67 return processed.replace( charsToRemove, QString() );
68}
69
70//
71// QgsFontManager
72//
73
75 : QObject( parent )
76{
77 const QStringList replacements = settingsFontFamilyReplacements->value();
78 for ( const QString &replacement : replacements )
79 {
80 const thread_local QRegularExpression rxReplacement( u"(.*?):(.*)"_s );
81 const QRegularExpressionMatch match = rxReplacement.match( replacement );
82 if ( match.hasMatch() )
83 {
84 mFamilyReplacements.insert( match.captured( 1 ), match.captured( 2 ) );
85 mLowerCaseFamilyReplacements.insert( match.captured( 1 ).toLower(), match.captured( 2 ) );
86 }
87 }
88}
89
90QMap<QString, QString> QgsFontManager::fontFamilyReplacements() const
91{
92 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Read );
93 return mFamilyReplacements;
94}
95
96void QgsFontManager::addFontFamilyReplacement( const QString &original, const QString &replacement )
97{
98 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Write );
99 if ( !replacement.isEmpty() )
100 {
101 mFamilyReplacements.insert( original, replacement );
102 mLowerCaseFamilyReplacements.insert( original.toLower(), replacement );
103 }
104 else
105 {
106 mFamilyReplacements.remove( original );
107 mLowerCaseFamilyReplacements.remove( original.toLower() );
108 }
109 storeFamilyReplacements();
110}
111
112void QgsFontManager::setFontFamilyReplacements( const QMap<QString, QString> &replacements )
113{
114 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Write );
115 mFamilyReplacements = replacements;
116 mLowerCaseFamilyReplacements.clear();
117 for ( auto it = mFamilyReplacements.constBegin(); it != mFamilyReplacements.constEnd(); ++it )
118 mLowerCaseFamilyReplacements.insert( it.key().toLower(), it.value() );
119
120 storeFamilyReplacements();
121}
122
123QString QgsFontManager::processFontFamilyName( const QString &name ) const
124{
125 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Read );
126 auto it = mLowerCaseFamilyReplacements.constFind( name.toLower() );
127 if ( it != mLowerCaseFamilyReplacements.constEnd() )
128 return it.value();
129 else
130 return name;
131}
132
133void QgsFontManager::storeFamilyReplacements()
134{
135 QStringList replacements;
136 for ( auto it = mFamilyReplacements.constBegin(); it != mFamilyReplacements.constEnd(); ++it )
137 replacements << u"%1:%2"_s.arg( it.key(), it.value() );
139}
140
142{
143 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Write );
144 const QString userProfileFontsDir = QgsApplication::qgisSettingsDirPath() + "fonts";
145 QStringList fontDirs { userProfileFontsDir };
146
147 fontDirs.append( mUserFontDirectories );
148
149 for ( const QString &dir : std::as_const( fontDirs ) )
150 {
151 if ( !QFile::exists( dir ) && !QDir().mkpath( dir ) )
152 {
153 QgsDebugError( u"Cannot create local fonts dir: %1"_s.arg( dir ) );
154 return;
155 }
156
157 installFontsFromDirectory( dir );
158 }
159}
160
161void QgsFontManager::installFontsFromDirectory( const QString &dir )
162{
163 const QFileInfoList fileInfoList = QDir( dir ).entryInfoList( QStringList( u"*"_s ), QDir::Files );
164 QFileInfoList::const_iterator infoIt = fileInfoList.constBegin();
165 for ( ; infoIt != fileInfoList.constEnd(); ++infoIt )
166 {
167 const int id = QFontDatabase::addApplicationFont( infoIt->filePath() );
168 if ( id == -1 )
169 {
170 QgsDebugError( u"The user font %1 could not be installed"_s.arg( infoIt->filePath() ) );
171 mUserFontToFamilyMap.remove( infoIt->filePath() );
172 mUserFontToIdMap.remove( infoIt->filePath() );
173 }
174 else
175 {
176 mUserFontToFamilyMap.insert( infoIt->filePath(), QFontDatabase::applicationFontFamilies( id ) );
177 mUserFontToIdMap.insert( infoIt->filePath(), id );
178 }
179 }
180}
181
182bool QgsFontManager::tryToDownloadFontFamily( const QString &family, QString &matchedFamily )
183{
184 matchedFamily.clear();
185 if ( !settingsDownloadMissingFonts->value() )
186 return false;
187
188 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Read );
189 auto it = mPendingFontDownloads.constFind( family );
190 if ( it != mPendingFontDownloads.constEnd() )
191 {
192 matchedFamily = it.value();
193 return true;
194 }
195 locker.unlock();
196
197 const QgsFontDownloadDetails details = detailsForFontDownload( family, matchedFamily );
198 if ( !details.isValid() )
199 return false;
200
201 // It's possible that the font family laundering applied in urlForFontDownload has cleaned up the font
202 // family to a valid font which already exists on the system. In this case we shouldn't try to download
203 // the font again.
204 const QFont testFont( matchedFamily );
205 if ( testFont.exactMatch() )
206 return true;
207
209 mPendingFontDownloads.insert( family, matchedFamily );
210 if ( !mEnableFontDownloads )
211 {
212 mDeferredFontDownloads.insert( matchedFamily, details );
213 }
214 else
215 {
216 locker.unlock();
217 downloadAndInstallFont( details, family );
218 }
219 return true;
220}
221
223{
224 if ( mEnableFontDownloads )
225 return;
226
227 mEnableFontDownloads = true;
228 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Read );
229 if ( !mDeferredFontDownloads.isEmpty() )
230 {
232 for ( auto it = mDeferredFontDownloads.constBegin(); it != mDeferredFontDownloads.constEnd(); ++it )
233 {
234 downloadAndInstallFont( it.value(), it.key() );
235 }
236 mDeferredFontDownloads.clear();
237 }
238}
239
240QgsFontDownloadDetails GoogleFontDetails( const QString &family, const QStringList &downloadPaths, const QString &licensePath = QString() )
241{
242 QStringList fontUrls;
243 fontUrls.reserve( downloadPaths.size() );
244 for ( const QString &path : downloadPaths )
245 {
246 fontUrls.append( u"https://github.com/google/fonts/raw/main/%1"_s.arg( path ) );
247 }
248 return QgsFontDownloadDetails( family, fontUrls, !licensePath.isEmpty() ? u"https://github.com/google/fonts/raw/main/%1"_s.arg( licensePath ) : QString() );
249}
250
251std::vector< QgsFontDownloadDetails > loadGoogleFontsFromJson()
252{
253 std::vector< QgsFontDownloadDetails > fonts;
254 // this json is built using scripts/process_google_fonts.py
255 const QString jsonPath = QgsApplication::pkgDataPath() + u"/resources/data/google_fonts.json"_s;
256
257 QFile file( jsonPath );
258 if ( !file.open( QIODevice::ReadOnly ) )
259 {
260 QgsDebugError( u"Failed to open Google fonts JSON file: %1"_s.arg( jsonPath ) );
261 return fonts;
262 }
263
264 const QByteArray jsonContent = file.readAll();
265 try
266 {
267 const json fontsJson = json::parse( jsonContent.toStdString() );
268 if ( fontsJson.is_array() )
269 {
270 fonts.reserve( fontsJson.size() );
271 for ( const json &fontJson : fontsJson )
272 {
273 const QString family = QString::fromStdString( fontJson["family"].get<std::string>() );
274 const QString license = QString::fromStdString( fontJson["license"].get<std::string>() );
275
276 QStringList paths;
277 const json &pathsArray = fontJson["paths"];
278 if ( !pathsArray.is_array() )
279 {
280 QgsDebugError( u"Failed to parse Google font %1, expected array for paths."_s.arg( family ) );
281 return fonts;
282 }
283 for ( const json &pathJson : pathsArray )
284 {
285 paths.append( QString::fromStdString( pathJson.get<std::string>() ) );
286 }
287
288 fonts.push_back( GoogleFontDetails( family, paths, license ) );
289 }
290 }
291 else
292 {
293 QgsDebugError( u"Failed to parse Google fonts JSON, expected array."_s );
294 return fonts;
295 }
296 }
297 catch ( nlohmann::json::exception &ex )
298 {
299 QgsDebugError( u"Failed to parse Google fonts JSON: %1"_s.arg( ex.what() ) );
300 return fonts;
301 }
302
303 return fonts;
304}
305
306QgsFontDownloadDetails QgsFontManager::detailsForFontDownload( const QString &family, QString &matchedFamily ) const
307{
308 static const std::vector< QgsFontDownloadDetails > sGoogleFonts = loadGoogleFontsFromJson();
309
310 matchedFamily.clear();
311 const QString cleanedFamily = QgsFontDownloadDetails::standardizeFamily( family );
312
313 for ( const QgsFontDownloadDetails &candidate : sGoogleFonts )
314 {
315 if ( candidate.standardizedFamily() == cleanedFamily )
316 {
317 matchedFamily = candidate.family();
318 return candidate;
319 }
320 }
321
322 return QgsFontDownloadDetails();
323}
324
325QString QgsFontManager::urlForFontDownload( const QString &family, QString &matchedFamily ) const
326{
327 const QgsFontDownloadDetails details = detailsForFontDownload( family, matchedFamily );
328 return details.isValid() ? details.fontUrls().value( 0 ) : QString();
329};
330
331void QgsFontManager::downloadAndInstallFont( const QgsFontDownloadDetails &details, const QString &identifier )
332{
333 if ( !details.isValid() )
334 return;
335
336 QString description;
337 if ( identifier.isEmpty() )
338 {
339 description = tr( "Installing %1" ).arg( details.family() );
340 }
341 else
342 {
343 description = tr( "Installing %1" ).arg( identifier );
344 }
345
346 QgsFontDownloadTask *task = new QgsFontDownloadTask( description, details );
347 connect( task, &QgsFontDownloadTask::taskTerminated, this, [this, task, identifier] {
348 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Write );
349 mPendingFontDownloads.remove( identifier );
350 locker.unlock();
351
352 emit fontDownloadErrorOccurred( QUrl( task->failedUrl() ), identifier, task->errorMessage() );
353 } );
354
355 connect( task, &QgsFontDownloadTask::taskCompleted, this, [this, task, details, identifier] {
356 const QList<QByteArray > allFontData = task->fontData();
357 QStringList allFamilies;
358 QStringList allLicenseDetails;
359
360 QString errorMessage;
361 for ( int i = 0; i < allFontData.size(); ++i )
362 {
363 QStringList thisUrlFamilies;
364 const QByteArray fontData = allFontData[i];
365 const QString contentDispositionFilename = task->contentDispositionFilenames().at( i );
366 QString extension;
367 if ( contentDispositionFilename.isEmpty() )
368 {
369 const QUrl originalUrl = details.fontUrls().value( i );
370 const thread_local QRegularExpression rxExtension( u"^.*\\.(\\w+?)$"_s );
371 extension = rxExtension.match( originalUrl.toString() ).captured( 1 );
372 }
373 QString thisLicenseDetails;
374 if ( !installFontsFromData( fontData, errorMessage, thisUrlFamilies, thisLicenseDetails, contentDispositionFilename, extension ) )
375 {
376 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Write );
377 mPendingFontDownloads.remove( identifier );
378 locker.unlock();
379
380 emit fontDownloadErrorOccurred( details.fontUrls().value( i ), identifier, errorMessage );
381 return;
382 }
383 else
384 {
385 for ( const QString &family : std::as_const( thisUrlFamilies ) )
386 {
387 if ( !allFamilies.contains( family ) )
388 allFamilies.append( family );
389 }
390 if ( !thisLicenseDetails.isEmpty() && !allLicenseDetails.contains( thisLicenseDetails ) )
391 {
392 allLicenseDetails.append( thisLicenseDetails );
393 }
394 }
395 }
396
397 if ( !task->licenseData().isEmpty() && !allLicenseDetails.contains( task->licenseData() ) )
398 {
399 allLicenseDetails.append( task->licenseData() );
400 }
401
402 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Write );
403 mPendingFontDownloads.remove( identifier );
404 locker.unlock();
405
406 emit fontDownloaded( allFamilies, allLicenseDetails.isEmpty() ? QString() : allLicenseDetails.join( "\n\n" ) );
407 } );
408
410}
411
412void QgsFontManager::downloadAndInstallFont( const QUrl &url, const QString &identifier )
413{
414 downloadAndInstallFont( QgsFontDownloadDetails( identifier, { url.toString() } ) );
415}
416
417bool QgsFontManager::installFontsFromData( const QByteArray &data, QString &errorMessage, QStringList &families, QString &licenseDetails, const QString &filename, const QString &extension )
418{
419 errorMessage.clear();
420 families.clear();
421 licenseDetails.clear();
422
423 QTemporaryFile tempFile;
424 if ( !extension.isEmpty() )
425 {
426 QString cleanedExtension = extension;
427 if ( cleanedExtension.startsWith( '.' ) )
428 cleanedExtension = cleanedExtension.mid( 1 );
429 tempFile.setFileTemplate( u"%1/XXXXXX.%2"_s.arg( QDir::tempPath(), cleanedExtension ) );
430 }
431 QTemporaryDir tempDir;
432
433 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Read );
434 const QString userFontsDir = mUserFontDirectories.empty() ? ( QgsApplication::qgisSettingsDirPath() + "fonts" ) : mUserFontDirectories.at( 0 );
435 locker.unlock();
436
437 const QDir fontsDir( userFontsDir );
438
439 if ( !tempFile.open() )
440 {
441 errorMessage = tr( "Could not write font data to a temporary file" );
442 return false;
443 }
444
445 tempFile.write( data );
446 tempFile.close();
447
448 QString sourcePath = tempFile.fileName();
449
450 //try to install the data directly as a font
451 int id = QFontDatabase::addApplicationFontFromData( data );
452 if ( id != -1 )
453 {
454 // successfully loaded data as a font
455 const QStringList foundFamilies = QFontDatabase::applicationFontFamilies( id );
456 // remove the application font, as we'll copy it to the final destination and re-add from there
457 QFontDatabase::removeApplicationFont( id );
458
459 if ( foundFamilies.empty() )
460 {
461 errorMessage = tr( "Could not find any families in font" );
462 return false;
463 }
464
465 QgsDebugMsgLevel( u"Found fonts %1"_s.arg( foundFamilies.join( ',' ) ), 2 );
466 families = foundFamilies;
467 // guess a good name for the file, by taking the first family name from the font
468 const QString family = families.at( 0 );
469 const QString destPath = QgsFileUtils::uniquePath( fontsDir.filePath( filename.isEmpty() ? family : filename ) );
470
471 if ( !QFile::copy( sourcePath, destPath ) )
472 {
473 errorMessage = tr( "Could not copy font to %1" ).arg( destPath );
474 return false;
475 }
476
477 id = QFontDatabase::addApplicationFont( destPath );
478 if ( id == -1 )
479 {
480 errorMessage = tr( "Could not install font from %1" ).arg( destPath );
481 return false;
482 }
483 else
484 {
486 mUserFontToFamilyMap.insert( destPath, foundFamilies );
487 mUserFontToIdMap.insert( destPath, id );
488 }
489 return true;
490 }
491 else
492 {
493 // font install failed, but maybe it's a zip file
494 QStringList files;
495 if ( QgsZipUtils::unzip( tempFile.fileName(), tempDir.path(), files ) )
496 {
498 for ( const QString &file : std::as_const( files ) )
499 {
500 const QFileInfo fi( file );
501 if ( fi.fileName().compare( "OFL.txt"_L1, Qt::CaseInsensitive ) == 0 || fi.fileName().compare( "LICENSE.txt"_L1, Qt::CaseInsensitive ) == 0 )
502 {
503 QFile licenseFile( file );
504 if ( licenseFile.open( QIODevice::ReadOnly ) )
505 {
506 QTextStream in( &licenseFile );
507 const QString license = in.readAll();
508 licenseDetails.append( license );
509 }
510 }
511 else if ( fi.suffix().compare( "ttf"_L1, Qt::CaseInsensitive ) == 0 || fi.suffix().compare( "otf"_L1, Qt::CaseInsensitive ) == 0 )
512 {
513 sourcePath = file;
514 id = QFontDatabase::addApplicationFont( sourcePath );
515 if ( id != -1 )
516 {
517 QFontDatabase::removeApplicationFont( id );
518 const QString destPath = fontsDir.filePath( fi.fileName() );
519 // dest path may already exist for zip files -- e.g if a single zip contains a number of font variants
520 if ( !QFile::exists( destPath ) && !QFile::copy( sourcePath, destPath ) )
521 {
522 errorMessage = tr( "Could not copy font to %1" ).arg( destPath );
523 return false;
524 }
525 id = QFontDatabase::addApplicationFont( destPath );
526 if ( id == -1 )
527 {
528 errorMessage = tr( "Could not install font from %1" ).arg( destPath );
529 return false;
530 }
531 const QStringList foundFamilies = QFontDatabase::applicationFontFamilies( id );
532 mUserFontToFamilyMap.insert( destPath, foundFamilies );
533 mUserFontToIdMap.insert( destPath, id );
534 for ( const QString &found : foundFamilies )
535 {
536 if ( !families.contains( found ) )
537 families << found;
538 }
539 }
540 }
541 }
542 return true;
543 }
544 }
545
546 errorMessage = tr( "Could not read fonts from data" );
547 return false;
548}
549
550void QgsFontManager::addUserFontDirectory( const QString &directory )
551{
552 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Read );
553 if ( mUserFontDirectories.contains( directory ) )
554 return;
555
557 mUserFontDirectories.append( directory );
558 locker.unlock();
559
560 if ( !QFile::exists( directory ) && !QDir().mkpath( directory ) )
561 {
562 QgsDebugError( u"Cannot create local fonts dir: %1"_s.arg( directory ) );
563 return;
564 }
565
566 installFontsFromDirectory( directory );
567}
568
569QMap<QString, QStringList> QgsFontManager::userFontToFamilyMap() const
570{
571 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Read );
572 return mUserFontToFamilyMap;
573}
574
575bool QgsFontManager::removeUserFont( const QString &path )
576{
577 QgsReadWriteLocker locker( mReplacementLock, QgsReadWriteLocker::Write );
578 const int id = mUserFontToIdMap.value( path, -1 );
579 if ( id != -1 )
580 QFontDatabase::removeApplicationFont( id );
581 QFile::remove( path );
582 mUserFontToIdMap.remove( path );
583 mUserFontToFamilyMap.remove( path );
584 return true;
585}
586
588//
589// QgsFontDownloadTask
590//
591
592QgsFontDownloadTask::QgsFontDownloadTask( const QString &description, const QgsFontDownloadDetails &details )
593 : QgsTask( description, QgsTask::CanCancel )
594 , mDetails( details )
595{}
596
597bool QgsFontDownloadTask::run()
598{
599 mFeedback = std::make_unique< QgsFeedback >();
600 mResult = true;
601
602 for ( const QString &url : mDetails.fontUrls() )
603 {
604 // TODO: We should really do this async, but I'm trying to minimize the impact of this change for backport friendliness
606 QNetworkRequest networkRequest( url );
607 QgsSetRequestInitiatorClass( networkRequest, u"QgsFontDownloadTask"_s );
608 switch ( req.get( networkRequest, false, mFeedback.get() ) )
609 {
611 mFontData.append( req.reply().content() );
612 mContentDispositionFilenames.append( QgsNetworkReplyContent::extractFileNameFromContentDispositionHeader( req.reply().rawHeader( "Content-Disposition" ) ) );
613 break;
614
618 mResult = false;
619 mErrorMessage = req.errorMessage();
620 mFailedUrl = url;
621 break;
622 }
623
624 if ( !mResult )
625 break;
626 }
627
628 if ( mResult && !mDetails.licenseUrl().isEmpty() )
629 {
631 QNetworkRequest networkRequest( mDetails.licenseUrl() );
632 QgsSetRequestInitiatorClass( networkRequest, u"QgsFontDownloadTask"_s );
633 switch ( req.get( networkRequest, false, mFeedback.get() ) )
634 {
636 mLicenseData = req.reply().content();
637 break;
638
642 mResult = false;
643 mErrorMessage = req.errorMessage();
644 mFailedUrl = mDetails.licenseUrl();
645 break;
646 }
647 }
648
649 return mResult;
650}
651
652void QgsFontDownloadTask::cancel()
653{
654 if ( mFeedback )
655 mFeedback->cancel();
657}
658
static QString pkgDataPath()
Returns the common root path of all application data directories.
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user's home dir.
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ NetworkError
A network error occurred.
@ ServerExceptionError
An exception was raised by the server.
@ NoError
No error was encountered.
@ TimeoutError
Timeout was reached before a reply was received.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Encapsulates details required for downloading a font.
static QString standardizeFamily(const QString &family)
Returns a cleaned, standardized version of a font family name.
QString licenseUrl() const
Returns the optional URL for downloading the font license details.
QStringList fontUrls() const
Returns a list of download URLs for all files associated with the font family.
QgsFontDownloadDetails()
Constructor for an invalid QgsFontDownloadDetails.
bool isValid() const
Returns true if the details represent a valid downloadable font.
QString family() const
Returns the font family.
void addFontFamilyReplacement(const QString &original, const QString &replacement)
Adds a new font replacement from the original font family to a replacement font family.
QgsFontDownloadDetails detailsForFontDownload(const QString &family, QString &matchedFamily) const
Returns a the details for downloading the specified font family.
bool removeUserFont(const QString &path)
Removes the user font at the specified path.
QMap< QString, QString > fontFamilyReplacements() const
Returns the map of automatic font family replacements.
void installUserFonts()
Installs user fonts from the profile/fonts directory as application fonts.
void fontDownloaded(const QStringList &families, const QString &licenseDetails)
Emitted when a font has downloaded and been locally loaded.
void addUserFontDirectory(const QString &directory)
Adds a directory to use for user fonts.
void setFontFamilyReplacements(const QMap< QString, QString > &replacements)
Sets the map of automatic font family replacements.
Q_DECL_DEPRECATED void downloadAndInstallFont(const QUrl &url, const QString &identifier=QString())
Downloads a font and installs in the user's profile/fonts directory as an application font,...
void enableFontDownloadsForSession()
Enables font downloads the current QGIS session.
Q_DECL_DEPRECATED QString urlForFontDownload(const QString &family, QString &matchedFamily) const
Returns the URL at which the font family can be downloaded.
static const QgsSettingsEntryStringList * settingsFontFamilyReplacements
Settings entry for font family replacements.
bool installFontsFromData(const QByteArray &data, QString &errorMessage, QStringList &families, QString &licenseDetails, const QString &filename=QString(), const QString &extension=QString())
Installs local user fonts from the specified raw data.
QString processFontFamilyName(const QString &name) const
Processes a font family name, applying any matching fontFamilyReplacements() to the name.
QMap< QString, QStringList > userFontToFamilyMap() const
Returns the mapping of installed user fonts to font families.
QgsFontManager(QObject *parent=nullptr)
Constructor for QgsFontManager, with the specified parent object.
static const QgsSettingsEntryBool * settingsDownloadMissingFonts
Settings entry for font family replacements.
bool tryToDownloadFontFamily(const QString &family, QString &matchedFamily)
Tries to download and install the specified font family.
void fontDownloadErrorOccurred(const QUrl &url, const QString &identifier, const QString &error)
Emitted when an error occurs during font downloading.
QByteArray content() const
Returns the reply content.
QByteArray rawHeader(const QByteArray &headerName) const
Returns the content of the header with the specified headerName, or an empty QByteArray if the specif...
static QString extractFileNameFromContentDispositionHeader(const QString &header)
Extracts the filename component of the content disposition header from the header.
A convenience class that simplifies locking and unlocking QReadWriteLocks.
@ Write
Lock for write.
void unlock()
Unlocks the lock.
void changeMode(Mode mode)
Change the mode of the lock to mode.
bool setValue(const T &value, const QString &dynamicKeyPart=QString()) const
Set settings value.
A boolean settings entry.
A string list settings entry.
static QgsSettingsTreeNode * sTreeFonts
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
Abstract base class for long running background tasks.
virtual void cancel()
Notifies the task that it should terminate.
static bool unzip(const QString &zip, const QString &dir, QStringList &files, bool checkConsistency=true)
Unzip a zip file in an output directory.
std::vector< QgsFontDownloadDetails > loadGoogleFontsFromJson()
QgsFontDownloadDetails GoogleFontDetails(const QString &family, const QStringList &downloadPaths, const QString &licensePath=QString())
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
#define QgsSetRequestInitiatorClass(request, _class)