1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qquickfontloader_p.h" |
41 | |
42 | #include <qqmlcontext.h> |
43 | #include <qqmlengine.h> |
44 | |
45 | #include <QStringList> |
46 | #include <QUrl> |
47 | #include <QDebug> |
48 | |
49 | #include <QFontDatabase> |
50 | |
51 | #include <private/qobject_p.h> |
52 | #include <qqmlinfo.h> |
53 | #include <qqmlfile.h> |
54 | |
55 | #if QT_CONFIG(qml_network) |
56 | #include <QNetworkRequest> |
57 | #include <QNetworkReply> |
58 | #endif |
59 | |
60 | #include <QtCore/QCoreApplication> |
61 | |
62 | QT_BEGIN_NAMESPACE |
63 | |
64 | #define FONTLOADER_MAXIMUM_REDIRECT_RECURSION 16 |
65 | |
66 | class QQuickFontObject : public QObject |
67 | { |
68 | Q_OBJECT |
69 | |
70 | public: |
71 | explicit QQuickFontObject(int _id = -1); |
72 | |
73 | #if QT_CONFIG(qml_network) |
74 | void download(const QUrl &url, QNetworkAccessManager *manager); |
75 | |
76 | Q_SIGNALS: |
77 | void fontDownloaded(const QString&, QQuickFontLoader::Status); |
78 | |
79 | private: |
80 | int redirectCount = 0; |
81 | QNetworkReply *reply = nullptr; |
82 | |
83 | private Q_SLOTS: |
84 | void replyFinished(); |
85 | #endif // qml_network |
86 | |
87 | public: |
88 | int id; |
89 | |
90 | Q_DISABLE_COPY(QQuickFontObject) |
91 | }; |
92 | |
93 | QQuickFontObject::QQuickFontObject(int _id) |
94 | : QObject(nullptr), id(_id) |
95 | { |
96 | } |
97 | |
98 | #if QT_CONFIG(qml_network) |
99 | void QQuickFontObject::download(const QUrl &url, QNetworkAccessManager *manager) |
100 | { |
101 | QNetworkRequest req(url); |
102 | req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true); |
103 | reply = manager->get(request: req); |
104 | QObject::connect(sender: reply, SIGNAL(finished()), receiver: this, SLOT(replyFinished())); |
105 | } |
106 | |
107 | void QQuickFontObject::replyFinished() |
108 | { |
109 | if (reply) { |
110 | redirectCount++; |
111 | if (redirectCount < FONTLOADER_MAXIMUM_REDIRECT_RECURSION) { |
112 | QVariant redirect = reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute); |
113 | if (redirect.isValid()) { |
114 | QUrl url = reply->url().resolved(relative: redirect.toUrl()); |
115 | QNetworkAccessManager *manager = reply->manager(); |
116 | reply->deleteLater(); |
117 | reply = nullptr; |
118 | download(url, manager); |
119 | return; |
120 | } |
121 | } |
122 | redirectCount = 0; |
123 | |
124 | if (!reply->error()) { |
125 | id = QFontDatabase::addApplicationFontFromData(fontData: reply->readAll()); |
126 | if (id != -1) |
127 | emit fontDownloaded(QFontDatabase::applicationFontFamilies(id).at(i: 0), QQuickFontLoader::Ready); |
128 | else |
129 | emit fontDownloaded(QString(), QQuickFontLoader::Error); |
130 | } else { |
131 | qWarning(msg: "%s: Unable to load font '%s': %s" , Q_FUNC_INFO, |
132 | qPrintable(reply->url().toString()), qPrintable(reply->errorString())); |
133 | emit fontDownloaded(QString(), QQuickFontLoader::Error); |
134 | } |
135 | reply->deleteLater(); |
136 | reply = nullptr; |
137 | } |
138 | } |
139 | #endif // qml_network |
140 | |
141 | class QQuickFontLoaderPrivate : public QObjectPrivate |
142 | { |
143 | Q_DECLARE_PUBLIC(QQuickFontLoader) |
144 | |
145 | public: |
146 | QQuickFontLoaderPrivate() {} |
147 | |
148 | QUrl url; |
149 | QString name; |
150 | QQuickFontLoader::Status status = QQuickFontLoader::Null; |
151 | }; |
152 | |
153 | static void q_QFontLoaderFontsStaticReset(); |
154 | static void q_QFontLoaderFontsAddReset() |
155 | { |
156 | qAddPostRoutine(q_QFontLoaderFontsStaticReset); |
157 | } |
158 | class QFontLoaderFonts |
159 | { |
160 | public: |
161 | QFontLoaderFonts() |
162 | { |
163 | qAddPostRoutine(q_QFontLoaderFontsStaticReset); |
164 | qAddPreRoutine(q_QFontLoaderFontsAddReset); |
165 | } |
166 | |
167 | ~QFontLoaderFonts() |
168 | { |
169 | qRemovePostRoutine(q_QFontLoaderFontsStaticReset); |
170 | reset(); |
171 | } |
172 | |
173 | |
174 | void reset() |
175 | { |
176 | QVector<QQuickFontObject *> deleted; |
177 | QHash<QUrl, QQuickFontObject*>::iterator it; |
178 | for (it = map.begin(); it != map.end(); ++it) { |
179 | if (!deleted.contains(t: it.value())) { |
180 | deleted.append(t: it.value()); |
181 | delete it.value(); |
182 | } |
183 | } |
184 | map.clear(); |
185 | } |
186 | |
187 | QHash<QUrl, QQuickFontObject *> map; |
188 | }; |
189 | Q_GLOBAL_STATIC(QFontLoaderFonts, fontLoaderFonts); |
190 | |
191 | static void q_QFontLoaderFontsStaticReset() |
192 | { |
193 | fontLoaderFonts()->reset(); |
194 | } |
195 | |
196 | /*! |
197 | \qmltype FontLoader |
198 | \instantiates QQuickFontLoader |
199 | \inqmlmodule QtQuick |
200 | \ingroup qtquick-text-utility |
201 | \brief Allows fonts to be loaded by name or URL. |
202 | |
203 | The FontLoader type is used to load fonts by name or URL. |
204 | |
205 | The \l status indicates when the font has been loaded, which is useful |
206 | for fonts loaded from remote sources. |
207 | |
208 | For example: |
209 | \qml |
210 | import QtQuick 2.0 |
211 | |
212 | Column { |
213 | FontLoader { id: fixedFont; name: "Courier" } |
214 | FontLoader { id: webFont; source: "http://www.mysite.com/myfont.ttf" } |
215 | |
216 | Text { text: "Fixed-size font"; font.family: fixedFont.name } |
217 | Text { text: "Fancy font"; font.family: webFont.name } |
218 | } |
219 | \endqml |
220 | |
221 | \sa {Qt Quick Examples - Text#Fonts}{Qt Quick Examples - Text Fonts} |
222 | */ |
223 | QQuickFontLoader::QQuickFontLoader(QObject *parent) |
224 | : QObject(*(new QQuickFontLoaderPrivate), parent) |
225 | { |
226 | } |
227 | |
228 | QQuickFontLoader::~QQuickFontLoader() |
229 | { |
230 | } |
231 | |
232 | /*! |
233 | \qmlproperty url QtQuick::FontLoader::source |
234 | The URL of the font to load. |
235 | */ |
236 | QUrl QQuickFontLoader::source() const |
237 | { |
238 | Q_D(const QQuickFontLoader); |
239 | return d->url; |
240 | } |
241 | |
242 | void QQuickFontLoader::setSource(const QUrl &url) |
243 | { |
244 | Q_D(QQuickFontLoader); |
245 | if (url == d->url) |
246 | return; |
247 | d->url = url; |
248 | emit sourceChanged(); |
249 | |
250 | QString localFile = QQmlFile::urlToLocalFileOrQrc(d->url); |
251 | if (!localFile.isEmpty()) { |
252 | if (!fontLoaderFonts()->map.contains(akey: d->url)) { |
253 | int id = QFontDatabase::addApplicationFont(fileName: localFile); |
254 | if (id != -1) { |
255 | updateFontInfo(QFontDatabase::applicationFontFamilies(id).at(i: 0), Ready); |
256 | QQuickFontObject *fo = new QQuickFontObject(id); |
257 | fontLoaderFonts()->map[d->url] = fo; |
258 | } else { |
259 | updateFontInfo(QString(), Error); |
260 | } |
261 | } else { |
262 | updateFontInfo(QFontDatabase::applicationFontFamilies(id: fontLoaderFonts()->map.value(akey: d->url)->id).at(i: 0), Ready); |
263 | } |
264 | } else { |
265 | if (!fontLoaderFonts()->map.contains(akey: d->url)) { |
266 | #if QT_CONFIG(qml_network) |
267 | QQuickFontObject *fo = new QQuickFontObject; |
268 | fontLoaderFonts()->map[d->url] = fo; |
269 | fo->download(url: d->url, manager: qmlEngine(this)->networkAccessManager()); |
270 | d->status = Loading; |
271 | emit statusChanged(); |
272 | QObject::connect(sender: fo, SIGNAL(fontDownloaded(QString,QQuickFontLoader::Status)), |
273 | receiver: this, SLOT(updateFontInfo(QString,QQuickFontLoader::Status))); |
274 | #else |
275 | // Silently fail if compiled with no_network |
276 | #endif |
277 | } else { |
278 | QQuickFontObject *fo = fontLoaderFonts()->map.value(akey: d->url); |
279 | if (fo->id == -1) { |
280 | #if QT_CONFIG(qml_network) |
281 | d->status = Loading; |
282 | emit statusChanged(); |
283 | QObject::connect(sender: fo, SIGNAL(fontDownloaded(QString,QQuickFontLoader::Status)), |
284 | receiver: this, SLOT(updateFontInfo(QString,QQuickFontLoader::Status))); |
285 | #else |
286 | // Silently fail if compiled with no_network |
287 | #endif |
288 | } |
289 | else |
290 | updateFontInfo(QFontDatabase::applicationFontFamilies(id: fo->id).at(i: 0), Ready); |
291 | } |
292 | } |
293 | } |
294 | |
295 | void QQuickFontLoader::updateFontInfo(const QString& name, QQuickFontLoader::Status status) |
296 | { |
297 | Q_D(QQuickFontLoader); |
298 | |
299 | if (name != d->name) { |
300 | d->name = name; |
301 | emit nameChanged(); |
302 | } |
303 | if (status != d->status) { |
304 | if (status == Error) |
305 | qmlWarning(me: this) << "Cannot load font: \"" << d->url.toString() << '"'; |
306 | d->status = status; |
307 | emit statusChanged(); |
308 | } |
309 | } |
310 | |
311 | /*! |
312 | \qmlproperty string QtQuick::FontLoader::name |
313 | |
314 | This property holds the name of the font family. |
315 | It is set automatically when a font is loaded using the \l source property. |
316 | |
317 | Use this to set the \c font.family property of a \c Text item. |
318 | |
319 | Example: |
320 | \qml |
321 | Item { |
322 | width: 200; height: 50 |
323 | |
324 | FontLoader { |
325 | id: webFont |
326 | source: "http://www.mysite.com/myfont.ttf" |
327 | } |
328 | Text { |
329 | text: "Fancy font" |
330 | font.family: webFont.name |
331 | } |
332 | } |
333 | \endqml |
334 | */ |
335 | QString QQuickFontLoader::name() const |
336 | { |
337 | Q_D(const QQuickFontLoader); |
338 | return d->name; |
339 | } |
340 | |
341 | void QQuickFontLoader::setName(const QString &name) |
342 | { |
343 | Q_D(QQuickFontLoader); |
344 | if (d->name == name) |
345 | return; |
346 | d->name = name; |
347 | emit nameChanged(); |
348 | d->status = Ready; |
349 | emit statusChanged(); |
350 | } |
351 | |
352 | /*! |
353 | \qmlproperty enumeration QtQuick::FontLoader::status |
354 | |
355 | This property holds the status of font loading. It can be one of: |
356 | \list |
357 | \li FontLoader.Null - no font has been set |
358 | \li FontLoader.Ready - the font has been loaded |
359 | \li FontLoader.Loading - the font is currently being loaded |
360 | \li FontLoader.Error - an error occurred while loading the font |
361 | \endlist |
362 | |
363 | Use this status to provide an update or respond to the status change in some way. |
364 | For example, you could: |
365 | |
366 | \list |
367 | \li Trigger a state change: |
368 | \qml |
369 | State { name: 'loaded'; when: loader.status == FontLoader.Ready } |
370 | \endqml |
371 | |
372 | \li Implement an \c onStatusChanged signal handler: |
373 | \qml |
374 | FontLoader { |
375 | id: loader |
376 | onStatusChanged: if (loader.status == FontLoader.Ready) console.log('Loaded') |
377 | } |
378 | \endqml |
379 | |
380 | \li Bind to the status value: |
381 | \qml |
382 | Text { text: loader.status == FontLoader.Ready ? 'Loaded' : 'Not loaded' } |
383 | \endqml |
384 | \endlist |
385 | */ |
386 | QQuickFontLoader::Status QQuickFontLoader::status() const |
387 | { |
388 | Q_D(const QQuickFontLoader); |
389 | return d->status; |
390 | } |
391 | |
392 | QT_END_NAMESPACE |
393 | |
394 | #include <qquickfontloader.moc> |
395 | |
396 | #include "moc_qquickfontloader_p.cpp" |
397 | |