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 | |
29 | QT_BEGIN_NAMESPACE |
30 | |
31 | #define FONTLOADER_MAXIMUM_REDIRECT_RECURSION 16 |
32 | |
33 | class QQuickFontObject : public QObject |
34 | { |
35 | Q_OBJECT |
36 | |
37 | public: |
38 | explicit QQuickFontObject(int _id = -1); |
39 | |
40 | #if QT_CONFIG(qml_network) |
41 | void download(const QUrl &url, QNetworkAccessManager *manager); |
42 | |
43 | Q_SIGNALS: |
44 | void fontDownloaded(int id); |
45 | |
46 | private: |
47 | int redirectCount = 0; |
48 | QNetworkReply *reply = nullptr; |
49 | |
50 | private Q_SLOTS: |
51 | void replyFinished(); |
52 | #endif // qml_network |
53 | |
54 | public: |
55 | int id; |
56 | |
57 | Q_DISABLE_COPY(QQuickFontObject) |
58 | }; |
59 | |
60 | QQuickFontObject::QQuickFontObject(int _id) |
61 | : QObject(nullptr), id(_id) |
62 | { |
63 | } |
64 | |
65 | #if QT_CONFIG(qml_network) |
66 | void 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 | |
74 | void 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 | |
105 | class QQuickFontLoaderPrivate : public QObjectPrivate |
106 | { |
107 | Q_DECLARE_PUBLIC(QQuickFontLoader) |
108 | |
109 | public: |
110 | QQuickFontLoaderPrivate() {} |
111 | |
112 | QUrl url; |
113 | QFont font; |
114 | QQuickFontLoader::Status status = QQuickFontLoader::Null; |
115 | }; |
116 | |
117 | static void q_QFontLoaderFontsStaticReset(); |
118 | static void q_QFontLoaderFontsAddReset() |
119 | { |
120 | qAddPostRoutine(q_QFontLoaderFontsStaticReset); |
121 | } |
122 | class QFontLoaderFonts |
123 | { |
124 | public: |
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 | }; |
150 | Q_GLOBAL_STATIC(QFontLoaderFonts, fontLoaderFonts); |
151 | |
152 | static 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 | */ |
182 | QQuickFontLoader::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 | */ |
192 | QUrl QQuickFontLoader::source() const |
193 | { |
194 | Q_D(const QQuickFontLoader); |
195 | return d->url; |
196 | } |
197 | |
198 | void 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 | |
252 | void 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 | */ |
341 | QFont 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 | */ |
374 | QString 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 | */ |
413 | QQuickFontLoader::Status QQuickFontLoader::status() const |
414 | { |
415 | Q_D(const QQuickFontLoader); |
416 | return d->status; |
417 | } |
418 | |
419 | QT_END_NAMESPACE |
420 | |
421 | #include <qquickfontloader.moc> |
422 | |
423 | #include "moc_qquickfontloader_p.cpp" |
424 | |