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