Download Google fonts from github, not broken Google fonts download url

Note that not all fonts available from Google fonts are present on
the github repo for some reason, eg Open Sans Condensed is not.

Fixes #57070
This commit is contained in:
Nyall Dawson 2024-04-12 11:21:23 +10:00
parent 62c5427f58
commit b70fba6762
5 changed files with 2144 additions and 1689 deletions

View File

@ -8,6 +8,69 @@
class QgsFontDownloadDetails
{
%Docstring(signature="appended")
Encapsulates details required for downloading a font.
.. versionadded:: 3.38
%End
%TypeHeaderCode
#include "qgsfontmanager.h"
%End
public:
QgsFontDownloadDetails();
%Docstring
Constructor for an invalid QgsFontDownloadDetails.
%End
QgsFontDownloadDetails( const QString &family, const QStringList &fontUrls, const QString &licenseUrl = QString() );
%Docstring
Constructor for QgsFontDownloadDetails.
:param family: Font family name
:param fontUrls: List of URLS to download for complete set of the font family resources
:param licenseUrl: optional URL to download the font license
%End
static QString standardizeFamily( const QString &family );
%Docstring
Returns a cleaned, standardized version of a font ``family`` name.
%End
bool isValid() const;
%Docstring
Returns ``True`` if the details represent a valid downloadable font.
%End
QString family() const;
%Docstring
Returns the font family.
.. seealso:: :py:func:`standardizedFamily`
%End
QString standardizedFamily() const;
%Docstring
Returns the cleaned, standardized font family name.
%End
QStringList fontUrls() const;
%Docstring
Returns a list of download URLs for all files associated with the font family.
%End
QString licenseUrl() const;
%Docstring
Returns the optional URL for downloading the font license details.
%End
};
class QgsFontManager : QObject
{
%Docstring(signature="appended")
@ -146,7 +209,7 @@ Enables font downloads the the current QGIS session.
Ensure that the :py:class:`QgsApplication` is fully initialized before calling this method.
%End
QString urlForFontDownload( const QString &family, QString &matchedFamily /Out/ ) const;
QString urlForFontDownload( const QString &family, QString &matchedFamily /Out/ ) const /Deprecated/;
%Docstring
Returns the URL at which the font ``family`` can be downloaded.
@ -157,11 +220,33 @@ return an empty string for any font families not present in this list.
:return: - URL to download font, or an empty string if no URL is available
- matchedFamily: will be set to found font family if a match was successful
.. deprecated:: QGIS 3.38
use :py:func:`~QgsFontManager.detailsForFontDownload` instead
%End
void downloadAndInstallFont( const QUrl &url, const QString &identifier = QString() );
QgsFontDownloadDetails detailsForFontDownload( const QString &family, QString &matchedFamily /Out/ ) const;
%Docstring
Downloads a font and installs in the user's profile/fonts directory as an application font.
Returns a the details for downloading the specified font ``family``.
The returned object will contain all URLs which must be fetched to retrieve the
entire font family (eg it may contain one URL per font style).
This method relies on a hardcoded list of available freely licensed fonts, and will
return an invalid :py:class:`QgsFontDownloadDetails` for any font families not present in this list.
:param family: input font family name to try to match to known fonts
:return: - details required for downloading font, or an invalid :py:class:`QgsFontDownloadDetails` if no URL is available
- matchedFamily: will be set to found font family if a match was successful
.. versionadded:: 3.38
%End
void downloadAndInstallFont( const QUrl &url, const QString &identifier = QString() ) /Deprecated/;
%Docstring
Downloads a font and installs in the user's profile/fonts directory as an application font,
where the font family can be downloaded via a single ``url``.
The download will proceed in a background task.
@ -171,6 +256,26 @@ tasks, e.g. the font family name if known.
.. seealso:: :py:func:`fontDownloaded`
.. seealso:: :py:func:`fontDownloadErrorOccurred`
.. deprecated:: QGIS 3.38
use the version which takes a :py:class:`QgsFontDownloadDetails` argument instead
%End
void downloadAndInstallFont( const QgsFontDownloadDetails &details, const QString &identifier = QString() );
%Docstring
Downloads a font and installs in the user's profile/fonts directory as an application font, where the
font family is split over multiple download URLs.
The download will proceed in a background task.
The optional ``identifier`` string can be used to specify a user-friendly name for the download
tasks, e.g. the font family name if known.
.. seealso:: :py:func:`fontDownloaded`
.. seealso:: :py:func:`fontDownloadErrorOccurred`
.. versionadded:: 3.38
%End
bool installFontsFromData( const QByteArray &data, QString &errorMessage /Out/, QStringList &families /Out/, QString &licenseDetails /Out/, const QString &filename = QString() );

View File

@ -8,6 +8,69 @@
class QgsFontDownloadDetails
{
%Docstring(signature="appended")
Encapsulates details required for downloading a font.
.. versionadded:: 3.38
%End
%TypeHeaderCode
#include "qgsfontmanager.h"
%End
public:
QgsFontDownloadDetails();
%Docstring
Constructor for an invalid QgsFontDownloadDetails.
%End
QgsFontDownloadDetails( const QString &family, const QStringList &fontUrls, const QString &licenseUrl = QString() );
%Docstring
Constructor for QgsFontDownloadDetails.
:param family: Font family name
:param fontUrls: List of URLS to download for complete set of the font family resources
:param licenseUrl: optional URL to download the font license
%End
static QString standardizeFamily( const QString &family );
%Docstring
Returns a cleaned, standardized version of a font ``family`` name.
%End
bool isValid() const;
%Docstring
Returns ``True`` if the details represent a valid downloadable font.
%End
QString family() const;
%Docstring
Returns the font family.
.. seealso:: :py:func:`standardizedFamily`
%End
QString standardizedFamily() const;
%Docstring
Returns the cleaned, standardized font family name.
%End
QStringList fontUrls() const;
%Docstring
Returns a list of download URLs for all files associated with the font family.
%End
QString licenseUrl() const;
%Docstring
Returns the optional URL for downloading the font license details.
%End
};
class QgsFontManager : QObject
{
%Docstring(signature="appended")
@ -146,7 +209,7 @@ Enables font downloads the the current QGIS session.
Ensure that the :py:class:`QgsApplication` is fully initialized before calling this method.
%End
QString urlForFontDownload( const QString &family, QString &matchedFamily /Out/ ) const;
QString urlForFontDownload( const QString &family, QString &matchedFamily /Out/ ) const /Deprecated/;
%Docstring
Returns the URL at which the font ``family`` can be downloaded.
@ -157,11 +220,33 @@ return an empty string for any font families not present in this list.
:return: - URL to download font, or an empty string if no URL is available
- matchedFamily: will be set to found font family if a match was successful
.. deprecated:: QGIS 3.38
use :py:func:`~QgsFontManager.detailsForFontDownload` instead
%End
void downloadAndInstallFont( const QUrl &url, const QString &identifier = QString() );
QgsFontDownloadDetails detailsForFontDownload( const QString &family, QString &matchedFamily /Out/ ) const;
%Docstring
Downloads a font and installs in the user's profile/fonts directory as an application font.
Returns a the details for downloading the specified font ``family``.
The returned object will contain all URLs which must be fetched to retrieve the
entire font family (eg it may contain one URL per font style).
This method relies on a hardcoded list of available freely licensed fonts, and will
return an invalid :py:class:`QgsFontDownloadDetails` for any font families not present in this list.
:param family: input font family name to try to match to known fonts
:return: - details required for downloading font, or an invalid :py:class:`QgsFontDownloadDetails` if no URL is available
- matchedFamily: will be set to found font family if a match was successful
.. versionadded:: 3.38
%End
void downloadAndInstallFont( const QUrl &url, const QString &identifier = QString() ) /Deprecated/;
%Docstring
Downloads a font and installs in the user's profile/fonts directory as an application font,
where the font family can be downloaded via a single ``url``.
The download will proceed in a background task.
@ -171,6 +256,26 @@ tasks, e.g. the font family name if known.
.. seealso:: :py:func:`fontDownloaded`
.. seealso:: :py:func:`fontDownloadErrorOccurred`
.. deprecated:: QGIS 3.38
use the version which takes a :py:class:`QgsFontDownloadDetails` argument instead
%End
void downloadAndInstallFont( const QgsFontDownloadDetails &details, const QString &identifier = QString() );
%Docstring
Downloads a font and installs in the user's profile/fonts directory as an application font, where the
font family is split over multiple download URLs.
The download will proceed in a background task.
The optional ``identifier`` string can be used to specify a user-friendly name for the download
tasks, e.g. the font family name if known.
.. seealso:: :py:func:`fontDownloaded`
.. seealso:: :py:func:`fontDownloadErrorOccurred`
.. versionadded:: 3.38
%End
bool installFontsFromData( const QByteArray &data, QString &errorMessage /Out/, QStringList &families /Out/, QString &licenseDetails /Out/, const QString &filename = QString() );

File diff suppressed because it is too large Load Diff

View File

@ -19,11 +19,110 @@
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgssettingsentryimpl.h"
#include "qgstaskmanager.h"
#include <QObject>
#include <QMap>
#include <QReadWriteLock>
#include <QSet>
/**
* \ingroup core
* \class QgsFontDownloadDetails
*
* \brief Encapsulates details required for downloading a font.
*
* \since QGIS 3.38
*/
class CORE_EXPORT QgsFontDownloadDetails
{
public:
/**
* Constructor for an invalid QgsFontDownloadDetails.
*/
QgsFontDownloadDetails();
/**
* Constructor for QgsFontDownloadDetails.
*
* \param family Font family name
* \param fontUrls List of URLS to download for complete set of the font family resources
* \param licenseUrl optional URL to download the font license
*/
QgsFontDownloadDetails( const QString &family, const QStringList &fontUrls, const QString &licenseUrl = QString() );
/**
* Returns a cleaned, standardized version of a font \a family name.
*/
static QString standardizeFamily( const QString &family );
/**
* Returns TRUE if the details represent a valid downloadable font.
*/
bool isValid() const { return !mFontUrls.empty(); }
/**
* Returns the font family.
*
* \see standardizedFamily()
*/
QString family() const { return mFamily; }
/**
* Returns the cleaned, standardized font family name.
*/
QString standardizedFamily() const { return mStandardizedFamily; }
/**
* Returns a list of download URLs for all files associated with the font family.
*/
QStringList fontUrls() const { return mFontUrls; }
/**
* Returns the optional URL for downloading the font license details.
*/
QString licenseUrl() const { return mLicenseUrl; }
private:
QString mFamily;
QString mStandardizedFamily;
QStringList mFontUrls;
QString mLicenseUrl;
};
#ifndef SIP_RUN
///@cond PRIVATE
class CORE_EXPORT QgsFontDownloadTask : public QgsTask
{
Q_OBJECT
public:
QgsFontDownloadTask( const QString &description, const QgsFontDownloadDetails &details );
bool run() override;
void cancel() override;
QString errorMessage() const { return mErrorMessage; }
QString failedUrl() const { return mFailedUrl; }
QList< QByteArray > fontData() const { return mFontData; }
QByteArray licenseData() const { return mLicenseData; }
QStringList contentDispositionFilenames() const { return mContentDispositionFilenames; }
private:
QgsFontDownloadDetails mDetails;
std::unique_ptr< QgsFeedback > mFeedback;
bool mResult = false;
QString mErrorMessage;
QString mFailedUrl;
QList< QByteArray > mFontData;
QStringList mContentDispositionFilenames;
QByteArray mLicenseData;
};
///@endcond PRIVATE
#endif
/**
* \ingroup core
* \class QgsFontManager
@ -164,11 +263,31 @@ class CORE_EXPORT QgsFontManager : public QObject
* \param family input font family name to try to match to known fonts
* \param matchedFamily will be set to found font family if a match was successful
* \returns URL to download font, or an empty string if no URL is available
*
* \deprecated Since QGIS 3.38, use detailsForFontDownload() instead
*/
QString urlForFontDownload( const QString &family, QString &matchedFamily SIP_OUT ) const;
Q_DECL_DEPRECATED QString urlForFontDownload( const QString &family, QString &matchedFamily SIP_OUT ) const SIP_DEPRECATED;
/**
* Downloads a font and installs in the user's profile/fonts directory as an application font.
* Returns a the details for downloading the specified font \a family.
*
* The returned object will contain all URLs which must be fetched to retrieve the
* entire font family (eg it may contain one URL per font style).
*
* This method relies on a hardcoded list of available freely licensed fonts, and will
* return an invalid QgsFontDownloadDetails for any font families not present in this list.
*
* \param family input font family name to try to match to known fonts
* \param matchedFamily will be set to found font family if a match was successful
* \returns details required for downloading font, or an invalid QgsFontDownloadDetails if no URL is available
*
* \since QGIS 3.38
*/
QgsFontDownloadDetails detailsForFontDownload( const QString &family, QString &matchedFamily SIP_OUT ) const;
/**
* Downloads a font and installs in the user's profile/fonts directory as an application font,
* where the font family can be downloaded via a single \a url.
*
* The download will proceed in a background task.
*
@ -177,8 +296,26 @@ class CORE_EXPORT QgsFontManager : public QObject
*
* \see fontDownloaded()
* \see fontDownloadErrorOccurred()
*
* \deprecated Since QGIS 3.38 use the version which takes a QgsFontDownloadDetails argument instead
*/
void downloadAndInstallFont( const QUrl &url, const QString &identifier = QString() );
Q_DECL_DEPRECATED void downloadAndInstallFont( const QUrl &url, const QString &identifier = QString() ) SIP_DEPRECATED;
/**
* Downloads a font and installs in the user's profile/fonts directory as an application font, where the
* font family is split over multiple download URLs.
*
* The download will proceed in a background task.
*
* The optional \a identifier string can be used to specify a user-friendly name for the download
* tasks, e.g. the font family name if known.
*
* \see fontDownloaded()
* \see fontDownloadErrorOccurred()
*
* \since QGIS 3.38
*/
void downloadAndInstallFont( const QgsFontDownloadDetails &details, const QString &identifier = QString() );
/**
* Installs local user fonts from the specified raw \a data.
@ -252,7 +389,7 @@ class CORE_EXPORT QgsFontManager : public QObject
bool mEnableFontDownloads = false;
QMap< QString, QString > mPendingFontDownloads;
QMap< QString, QString > mDeferredFontDownloads;
QMap< QString, QgsFontDownloadDetails > mDeferredFontDownloads;
void storeFamilyReplacements();
void installFontsFromDirectory( const QString &dir );

View File

@ -187,59 +187,64 @@ class TestQgsFontManager(QgisTestCase):
self.assertEqual(manager.userFontToFamilyMap(), {os.path.join(user_font_dir, 'Fresca-Regular.ttf'): ['Fresca']})
@unittest.skip('Temporarily disabled')
def test_font_download_url(self):
def test_font_download_urls(self):
manager = QgsFontManager()
self.assertEqual(manager.urlForFontDownload('xxx'), ('', ''))
self.assertEqual(manager.urlForFontDownload('Alegreya SC'), ('https://fonts.google.com/download?family=Alegreya+SC', 'Alegreya SC'))
self.assertEqual(manager.urlForFontDownload('AlegreyaSC'), ('https://fonts.google.com/download?family=Alegreya+SC', 'Alegreya SC'))
self.assertEqual(manager.urlForFontDownload('alegreya_sc'), ('https://fonts.google.com/download?family=Alegreya+SC', 'Alegreya SC'))
self.assertFalse(manager.detailsForFontDownload('xxx')[0].isValid())
self.assertEqual(manager.urlForFontDownload('Alegreya SC'), ('https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-Regular.ttf', 'Alegreya SC'))
self.assertEqual(manager.urlForFontDownload('AlegreyaSC'), ('https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-Regular.ttf', 'Alegreya SC'))
self.assertEqual(manager.urlForFontDownload('alegreya_sc'), ('https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-Regular.ttf', 'Alegreya SC'))
self.assertTrue(manager.detailsForFontDownload('Alegreya SC')[0].isValid())
self.assertEqual(manager.detailsForFontDownload('Alegreya SC')[0].fontUrls(), ['https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-Regular.ttf', 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-Italic.ttf', 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-Medium.ttf', 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-MediumItalic.ttf', 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-Bold.ttf', 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-BoldItalic.ttf', 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-ExtraBold.ttf', 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-ExtraBoldItalic.ttf', 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-Black.ttf', 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/AlegreyaSC-BlackItalic.ttf'])
self.assertEqual(manager.detailsForFontDownload('Alegreya SC')[0].licenseUrl(), 'https://github.com/google/fonts/raw/main/ofl/alegreyasc/OFL.txt')
self.assertEqual(manager.urlForFontDownload('Roboto'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Open Sans'),
('https://fonts.google.com/download?family=Open+Sans', 'Open Sans'))
('https://github.com/google/fonts/raw/main/ofl/opensans/OpenSans[wdth,wght].ttf', 'Open Sans'))
self.assertEqual(manager.urlForFontDownload('Open Sans Condensed'),
('https://fonts.google.com/download?family=Open+Sans+Condensed', 'Open Sans Condensed'))
# not available via github?
# self.assertEqual(manager.urlForFontDownload('Open Sans Condensed'),
# ('https://fonts.google.com/download?family=Open+Sans+Condensed', 'Open Sans Condensed'))
self.assertEqual(manager.urlForFontDownload('Noto Sans'),
('https://fonts.google.com/download?family=Noto+Sans', 'Noto Sans'))
('https://github.com/google/fonts/raw/main/ofl/notosans/NotoSans[wdth,wght].ttf', 'Noto Sans'))
self.assertEqual(manager.urlForFontDownload('Roboto Condensed'), ('https://fonts.google.com/download?family=Roboto+Condensed', 'Roboto Condensed'))
self.assertEqual(manager.urlForFontDownload('Roboto Condensed'), ('https://github.com/google/fonts/raw/main/ofl/robotocondensed/RobotoCondensed[wght].ttf', 'Roboto Condensed'))
# variants for font names typically seen in vector tile styles
self.assertEqual(manager.urlForFontDownload('RobotoCondensedRegular'), ('https://fonts.google.com/download?family=Roboto+Condensed', 'Roboto Condensed'))
self.assertEqual(manager.urlForFontDownload('Roboto Condensed Regular'), ('https://fonts.google.com/download?family=Roboto+Condensed', 'Roboto Condensed'))
self.assertEqual(manager.urlForFontDownload('RobotoCondensedRegular'), ('https://github.com/google/fonts/raw/main/ofl/robotocondensed/RobotoCondensed[wght].ttf', 'Roboto Condensed'))
self.assertEqual(manager.urlForFontDownload('Roboto Condensed Regular'), ('https://github.com/google/fonts/raw/main/ofl/robotocondensed/RobotoCondensed[wght].ttf', 'Roboto Condensed'))
self.assertEqual(manager.urlForFontDownload('Roboto_Condensed_Regular'),
('https://fonts.google.com/download?family=Roboto+Condensed', 'Roboto Condensed'))
('https://github.com/google/fonts/raw/main/ofl/robotocondensed/RobotoCondensed[wght].ttf', 'Roboto Condensed'))
# with style names
self.assertEqual(manager.urlForFontDownload('Roboto Black'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Black Italic'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Bold'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Bold Italic'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Italic'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Light'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Light Italic'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Medium'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Medium Italic'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Regular'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Thin'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
self.assertEqual(manager.urlForFontDownload('Roboto Thin Italic'),
('https://fonts.google.com/download?family=Roboto', 'Roboto'))
('https://github.com/google/fonts/raw/main/ofl/roboto/Roboto[wdth,wght].ttf', 'Roboto'))
if __name__ == '__main__':