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
62QT_BEGIN_NAMESPACE
63
64#define FONTLOADER_MAXIMUM_REDIRECT_RECURSION 16
65
66class QQuickFontObject : public QObject
67{
68Q_OBJECT
69
70public:
71 explicit QQuickFontObject(int _id = -1);
72
73#if QT_CONFIG(qml_network)
74 void download(const QUrl &url, QNetworkAccessManager *manager);
75
76Q_SIGNALS:
77 void fontDownloaded(const QString&, QQuickFontLoader::Status);
78
79private:
80 int redirectCount = 0;
81 QNetworkReply *reply = nullptr;
82
83private Q_SLOTS:
84 void replyFinished();
85#endif // qml_network
86
87public:
88 int id;
89
90 Q_DISABLE_COPY(QQuickFontObject)
91};
92
93QQuickFontObject::QQuickFontObject(int _id)
94 : QObject(nullptr), id(_id)
95{
96}
97
98#if QT_CONFIG(qml_network)
99void QQuickFontObject::download(const QUrl &url, QNetworkAccessManager *manager)
100{
101 QNetworkRequest req(url);
102 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
103 reply = manager->get(req);
104 QObject::connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
105}
106
107void QQuickFontObject::replyFinished()
108{
109 if (reply) {
110 redirectCount++;
111 if (redirectCount < FONTLOADER_MAXIMUM_REDIRECT_RECURSION) {
112 QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
113 if (redirect.isValid()) {
114 QUrl url = reply->url().resolved(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(reply->readAll());
126 if (id != -1)
127 emit fontDownloaded(QFontDatabase::applicationFontFamilies(id).at(0), QQuickFontLoader::Ready);
128 else
129 emit fontDownloaded(QString(), QQuickFontLoader::Error);
130 } else {
131 qWarning("%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
141class QQuickFontLoaderPrivate : public QObjectPrivate
142{
143 Q_DECLARE_PUBLIC(QQuickFontLoader)
144
145public:
146 QQuickFontLoaderPrivate() {}
147
148 QUrl url;
149 QString name;
150 QQuickFontLoader::Status status = QQuickFontLoader::Null;
151};
152
153static void q_QFontLoaderFontsStaticReset();
154static void q_QFontLoaderFontsAddReset()
155{
156 qAddPostRoutine(q_QFontLoaderFontsStaticReset);
157}
158class QFontLoaderFonts
159{
160public:
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(it.value())) {
180 deleted.append(it.value());
181 delete it.value();
182 }
183 }
184 map.clear();
185 }
186
187 QHash<QUrl, QQuickFontObject *> map;
188};
189Q_GLOBAL_STATIC(QFontLoaderFonts, fontLoaderFonts);
190
191static 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*/
223QQuickFontLoader::QQuickFontLoader(QObject *parent)
224 : QObject(*(new QQuickFontLoaderPrivate), parent)
225{
226}
227
228QQuickFontLoader::~QQuickFontLoader()
229{
230}
231
232/*!
233 \qmlproperty url QtQuick::FontLoader::source
234 The URL of the font to load.
235*/
236QUrl QQuickFontLoader::source() const
237{
238 Q_D(const QQuickFontLoader);
239 return d->url;
240}
241
242void 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(d->url)) {
253 int id = QFontDatabase::addApplicationFont(localFile);
254 if (id != -1) {
255 updateFontInfo(QFontDatabase::applicationFontFamilies(id).at(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(fontLoaderFonts()->map.value(d->url)->id).at(0), Ready);
263 }
264 } else {
265 if (!fontLoaderFonts()->map.contains(d->url)) {
266#if QT_CONFIG(qml_network)
267 QQuickFontObject *fo = new QQuickFontObject;
268 fontLoaderFonts()->map[d->url] = fo;
269 fo->download(d->url, qmlEngine(this)->networkAccessManager());
270 d->status = Loading;
271 emit statusChanged();
272 QObject::connect(fo, SIGNAL(fontDownloaded(QString,QQuickFontLoader::Status)),
273 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(d->url);
279 if (fo->id == -1) {
280#if QT_CONFIG(qml_network)
281 d->status = Loading;
282 emit statusChanged();
283 QObject::connect(fo, SIGNAL(fontDownloaded(QString,QQuickFontLoader::Status)),
284 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(fo->id).at(0), Ready);
291 }
292 }
293}
294
295void 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(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*/
335QString QQuickFontLoader::name() const
336{
337 Q_D(const QQuickFontLoader);
338 return d->name;
339}
340
341void 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*/
386QQuickFontLoader::Status QQuickFontLoader::status() const
387{
388 Q_D(const QQuickFontLoader);
389 return d->status;
390}
391
392QT_END_NAMESPACE
393
394#include <qquickfontloader.moc>
395
396#include "moc_qquickfontloader_p.cpp"
397