1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickfontloader_p.h"
5
6#include <qqmlcontext.h>
7#include <qqmlengine.h>
8
9#include <QStringList>
10#include <QUrl>
11#include <QDebug>
12
13#include <QFontDatabase>
14
15#include <private/qobject_p.h>
16#include <qqmlinfo.h>
17#include <qqmlfile.h>
18
19#if QT_CONFIG(qml_network)
20#include <QNetworkRequest>
21#include <QNetworkReply>
22#endif
23
24#include <QtCore/QCoreApplication>
25#include <QtCore/private/qduplicatetracker_p.h>
26
27#include <QtGui/private/qfontdatabase_p.h>
28
29QT_BEGIN_NAMESPACE
30
31#define FONTLOADER_MAXIMUM_REDIRECT_RECURSION 16
32
33class QQuickFontObject : public QObject
34{
35Q_OBJECT
36
37public:
38 explicit QQuickFontObject(int _id = -1);
39
40#if QT_CONFIG(qml_network)
41 void download(const QUrl &url, QNetworkAccessManager *manager);
42
43Q_SIGNALS:
44 void fontDownloaded(int id);
45
46private:
47 int redirectCount = 0;
48 QNetworkReply *reply = nullptr;
49
50private Q_SLOTS:
51 void replyFinished();
52#endif // qml_network
53
54public:
55 int id;
56
57 Q_DISABLE_COPY(QQuickFontObject)
58};
59
60QQuickFontObject::QQuickFontObject(int _id)
61 : QObject(nullptr), id(_id)
62{
63}
64
65#if QT_CONFIG(qml_network)
66void QQuickFontObject::download(const QUrl &url, QNetworkAccessManager *manager)
67{
68 QNetworkRequest req(url);
69 req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true);
70 reply = manager->get(request: req);
71 QObject::connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished()));
72}
73
74void QQuickFontObject::replyFinished()
75{
76 if (reply) {
77 redirectCount++;
78 if (redirectCount < FONTLOADER_MAXIMUM_REDIRECT_RECURSION) {
79 QVariant redirect = reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute);
80 if (redirect.isValid()) {
81 QUrl url = reply->url().resolved(relative: redirect.toUrl());
82 QNetworkAccessManager *manager = reply->manager();
83 reply->deleteLater();
84 reply = nullptr;
85 download(url, manager);
86 return;
87 }
88 }
89 redirectCount = 0;
90
91 if (!reply->error()) {
92 id = QFontDatabase::addApplicationFontFromData(fontData: reply->readAll());
93 emit fontDownloaded(id);
94 } else {
95 qWarning(msg: "%s: Unable to load font '%s': %s", Q_FUNC_INFO,
96 qPrintable(reply->url().toString()), qPrintable(reply->errorString()));
97 emit fontDownloaded(id: -1);
98 }
99 reply->deleteLater();
100 reply = nullptr;
101 }
102}
103#endif // qml_network
104
105class QQuickFontLoaderPrivate : public QObjectPrivate
106{
107 Q_DECLARE_PUBLIC(QQuickFontLoader)
108
109public:
110 QQuickFontLoaderPrivate() {}
111
112 QUrl url;
113 QFont font;
114 QQuickFontLoader::Status status = QQuickFontLoader::Null;
115};
116
117static void q_QFontLoaderFontsStaticReset();
118static void q_QFontLoaderFontsAddReset()
119{
120 qAddPostRoutine(q_QFontLoaderFontsStaticReset);
121}
122class QFontLoaderFonts
123{
124public:
125 QFontLoaderFonts()
126 {
127 qAddPostRoutine(q_QFontLoaderFontsStaticReset);
128 qAddPreRoutine(q_QFontLoaderFontsAddReset);
129 }
130
131 ~QFontLoaderFonts()
132 {
133 qRemovePostRoutine(q_QFontLoaderFontsStaticReset);
134 reset();
135 }
136
137
138 void reset()
139 {
140 QDuplicateTracker<QQuickFontObject *, 256> deleted(map.size());
141 for (QQuickFontObject *fo : std::as_const(t&: map)) {
142 if (!deleted.hasSeen(s: fo))
143 delete fo;
144 }
145 map.clear();
146 }
147
148 QHash<QUrl, QQuickFontObject *> map;
149};
150Q_GLOBAL_STATIC(QFontLoaderFonts, fontLoaderFonts);
151
152static void q_QFontLoaderFontsStaticReset()
153{
154 fontLoaderFonts()->reset();
155}
156
157/*!
158 \qmltype FontLoader
159 \instantiates QQuickFontLoader
160 \inqmlmodule QtQuick
161 \ingroup qtquick-text-utility
162 \brief Allows fonts to be loaded by URL.
163
164 The FontLoader type is used to load fonts by URL.
165
166 The \l status indicates when the font has been loaded, which is useful
167 for fonts loaded from remote sources.
168
169 For example:
170 \qml
171 import QtQuick 2.0
172
173 Column {
174 FontLoader { id: webFont; source: "http://www.mysite.com/myfont.ttf" }
175
176 Text { text: "Fancy font"; font: webFont.font }
177 }
178 \endqml
179
180 \sa {Qt Quick Examples - Text#Fonts}{Qt Quick Examples - Text Fonts}
181*/
182QQuickFontLoader::QQuickFontLoader(QObject *parent)
183 : QObject(*(new QQuickFontLoaderPrivate), parent)
184{
185 connect(sender: this, signal: &QQuickFontLoader::fontChanged, context: this, slot: &QQuickFontLoader::nameChanged);
186}
187
188/*!
189 \qmlproperty url QtQuick::FontLoader::source
190 The URL of the font to load.
191*/
192QUrl QQuickFontLoader::source() const
193{
194 Q_D(const QQuickFontLoader);
195 return d->url;
196}
197
198void QQuickFontLoader::setSource(const QUrl &url)
199{
200 Q_D(QQuickFontLoader);
201 if (url == d->url)
202 return;
203 d->url = url;
204 emit sourceChanged();
205
206 const QQmlContext *context = qmlContext(this);
207 const QUrl &resolvedUrl = context ? context->resolvedUrl(d->url) : d->url;
208 QString localFile = QQmlFile::urlToLocalFileOrQrc(resolvedUrl);
209 if (!localFile.isEmpty()) {
210 if (!fontLoaderFonts()->map.contains(key: resolvedUrl)) {
211 int id = QFontDatabase::addApplicationFont(fileName: localFile);
212 updateFontInfo(id);
213 if (id != -1) {
214 QQuickFontObject *fo = new QQuickFontObject(id);
215 fontLoaderFonts()->map[resolvedUrl] = fo;
216 }
217 } else {
218 updateFontInfo(fontLoaderFonts()->map.value(key: resolvedUrl)->id);
219 }
220 } else {
221 if (!fontLoaderFonts()->map.contains(key: resolvedUrl)) {
222 Q_ASSERT(context);
223#if QT_CONFIG(qml_network)
224 QQuickFontObject *fo = new QQuickFontObject;
225 fontLoaderFonts()->map[resolvedUrl] = fo;
226 fo->download(url: resolvedUrl, manager: context->engine()->networkAccessManager());
227 d->status = Loading;
228 emit statusChanged();
229 QObject::connect(sender: fo, SIGNAL(fontDownloaded(int)),
230 receiver: this, SLOT(updateFontInfo(int)));
231#else
232// Silently fail if compiled with no_network
233#endif
234 } else {
235 QQuickFontObject *fo = fontLoaderFonts()->map.value(key: resolvedUrl);
236 if (fo->id == -1) {
237#if QT_CONFIG(qml_network)
238 d->status = Loading;
239 emit statusChanged();
240 QObject::connect(sender: fo, SIGNAL(fontDownloaded(int)),
241 receiver: this, SLOT(updateFontInfo(int)));
242#else
243// Silently fail if compiled with no_network
244#endif
245 }
246 else
247 updateFontInfo(fo->id);
248 }
249 }
250}
251
252void QQuickFontLoader::updateFontInfo(int id)
253{
254 Q_D(QQuickFontLoader);
255
256 QFont font;
257
258 QQuickFontLoader::Status status = Error;
259 if (id >= 0) {
260 QFontDatabasePrivate *p = QFontDatabasePrivate::instance();
261 if (id < p->applicationFonts.size()) {
262 const QFontDatabasePrivate::ApplicationFont &applicationFont = p->applicationFonts.at(i: id);
263
264 if (!applicationFont.properties.isEmpty()) {
265 const QFontDatabasePrivate::ApplicationFont::Properties &properties = applicationFont.properties.at(i: 0);
266 font.setFamily(properties.familyName);
267 font.setStyleName(properties.styleName);
268 font.setWeight(QFont::Weight(properties.weight));
269 font.setStyle(properties.style);
270 font.setStretch(properties.stretch);
271 }
272 }
273
274 status = Ready;
275 }
276
277 if (font != d->font) {
278 d->font = font;
279 emit fontChanged();
280 }
281
282 if (status != d->status) {
283 if (status == Error) {
284 const QQmlContext *context = qmlContext(this);
285 qmlWarning(me: this) << "Cannot load font: \""
286 << (context ? context->resolvedUrl(d->url) : d->url).toString() << '"';
287 }
288 d->status = status;
289 emit statusChanged();
290 }
291}
292
293/*!
294 \qmlproperty font QtQuick::FontLoader::font
295 \since 6.0
296
297 This property holds a default query for the loaded font.
298
299 You can use this to select the font if other properties than just the
300 family name are needed to disambiguate. You can either specify the
301 font using individual properties:
302
303 \qml
304 Item {
305 width: 200; height: 50
306
307 FontLoader {
308 id: webFont
309 source: "http://www.mysite.com/myfont.ttf"
310 }
311 Text {
312 text: "Fancy font"
313 font.family: webFont.font.family
314 font.weight: webFont.font.weight
315 font.styleName: webFont.font.styleName
316 font.pixelSize: 24
317 }
318 }
319 \endqml
320
321 Or you can set the full font query directly:
322
323 \qml
324 Item {
325 width: 200; height: 50
326
327 FontLoader {
328 id: webFont
329 source: "http://www.mysite.com/myfont.ttf"
330 }
331 Text {
332 text: "Fancy font"
333 font: webFont.font
334 }
335 }
336 \endqml
337
338 In this case, the default font query will be used with no modifications
339 (so font size, for instance, will be the system default).
340*/
341QFont QQuickFontLoader::font() const
342{
343 Q_D(const QQuickFontLoader);
344 return d->font;
345}
346
347/*!
348 \qmlproperty string QtQuick::FontLoader::name
349 \readonly
350
351 This property holds the name of the font family.
352 It is set automatically when a font is loaded using the \l source property.
353
354 This is equivalent to the family property of the FontLoader's \l font property.
355
356 Use this to set the \c font.family property of a \c Text item.
357
358 Example:
359 \qml
360 Item {
361 width: 200; height: 50
362
363 FontLoader {
364 id: webFont
365 source: "http://www.mysite.com/myfont.ttf"
366 }
367 Text {
368 text: "Fancy font"
369 font.family: webFont.name
370 }
371 }
372 \endqml
373*/
374QString QQuickFontLoader::name() const
375{
376 Q_D(const QQuickFontLoader);
377 return d->font.resolveMask() == 0 ? QString() : d->font.family();
378}
379
380/*!
381 \qmlproperty enumeration QtQuick::FontLoader::status
382
383 This property holds the status of font loading. It can be one of:
384
385 \value FontLoader.Null no font has been set
386 \value FontLoader.Ready the font has been loaded
387 \value FontLoader.Loading the font is currently being loaded
388 \value FontLoader.Error an error occurred while loading the font
389
390 Use this status to provide an update or respond to the status change in some way.
391 For example, you could:
392
393 \list
394 \li Trigger a state change:
395 \qml
396 State { name: 'loaded'; when: loader.status == FontLoader.Ready }
397 \endqml
398
399 \li Implement an \c onStatusChanged signal handler:
400 \qml
401 FontLoader {
402 id: loader
403 onStatusChanged: if (loader.status == FontLoader.Ready) console.log('Loaded')
404 }
405 \endqml
406
407 \li Bind to the status value:
408 \qml
409 Text { text: loader.status == FontLoader.Ready ? 'Loaded' : 'Not loaded' }
410 \endqml
411 \endlist
412*/
413QQuickFontLoader::Status QQuickFontLoader::status() const
414{
415 Q_D(const QQuickFontLoader);
416 return d->status;
417}
418
419QT_END_NAMESPACE
420
421#include <qquickfontloader.moc>
422
423#include "moc_qquickfontloader_p.cpp"
424

source code of qtdeclarative/src/quick/util/qquickfontloader.cpp