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 test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtTest/QtTest> |
30 | |
31 | #include <qfontdatabase.h> |
32 | #include <qfontinfo.h> |
33 | #include <qfontmetrics.h> |
34 | #include <qtextlayout.h> |
35 | #include <private/qrawfont_p.h> |
36 | #include <private/qfont_p.h> |
37 | #include <private/qfontengine_p.h> |
38 | #include <qpa/qplatformfontdatabase.h> |
39 | |
40 | Q_LOGGING_CATEGORY(lcTests, "qt.text.tests" ) |
41 | |
42 | class tst_QFontDatabase : public QObject |
43 | { |
44 | Q_OBJECT |
45 | |
46 | public: |
47 | tst_QFontDatabase(); |
48 | |
49 | private slots: |
50 | void initTestCase(); |
51 | void styles_data(); |
52 | void styles(); |
53 | |
54 | void fixedPitch_data(); |
55 | void fixedPitch(); |
56 | void systemFixedFont(); |
57 | |
58 | #ifdef Q_OS_MAC |
59 | void trickyFonts_data(); |
60 | void trickyFonts(); |
61 | #endif |
62 | |
63 | void widthTwoTimes_data(); |
64 | void widthTwoTimes(); |
65 | |
66 | void addAppFont_data(); |
67 | void addAppFont(); |
68 | |
69 | void addTwoAppFontsFromFamily(); |
70 | |
71 | void aliases(); |
72 | void fallbackFonts(); |
73 | |
74 | void condensedFontWidth(); |
75 | void condensedFontWidthNoFontMerging(); |
76 | void condensedFontMatching(); |
77 | |
78 | void rasterFonts(); |
79 | void smoothFonts(); |
80 | |
81 | void registerOpenTypePreferredNamesSystem(); |
82 | void registerOpenTypePreferredNamesApplication(); |
83 | |
84 | private: |
85 | QString m_ledFont; |
86 | QString m_testFont; |
87 | QString m_testFontCondensed; |
88 | QString m_testFontItalic; |
89 | }; |
90 | |
91 | tst_QFontDatabase::tst_QFontDatabase() |
92 | { |
93 | } |
94 | |
95 | void tst_QFontDatabase::initTestCase() |
96 | { |
97 | m_ledFont = QFINDTESTDATA("LED_REAL.TTF" ); |
98 | m_testFont = QFINDTESTDATA("testfont.ttf" ); |
99 | m_testFontCondensed = QFINDTESTDATA("testfont_condensed.ttf" ); |
100 | m_testFontItalic = QFINDTESTDATA("testfont_italic.ttf" ); |
101 | QVERIFY(!m_ledFont.isEmpty()); |
102 | QVERIFY(!m_testFont.isEmpty()); |
103 | QVERIFY(!m_testFontCondensed.isEmpty()); |
104 | QVERIFY(!m_testFontItalic.isEmpty()); |
105 | } |
106 | |
107 | void tst_QFontDatabase::styles_data() |
108 | { |
109 | QTest::addColumn<QString>(name: "font" ); |
110 | |
111 | QTest::newRow( dataTag: "data0" ) << QString( "Times New Roman" ); |
112 | } |
113 | |
114 | void tst_QFontDatabase::styles() |
115 | { |
116 | QFETCH( QString, font ); |
117 | |
118 | QFontDatabase fdb; |
119 | QStringList styles = fdb.styles( family: font ); |
120 | QStringList::Iterator it = styles.begin(); |
121 | while ( it != styles.end() ) { |
122 | QString style = *it; |
123 | QString trimmed = style.trimmed(); |
124 | ++it; |
125 | |
126 | QCOMPARE(style, trimmed); |
127 | } |
128 | } |
129 | |
130 | void tst_QFontDatabase::fixedPitch_data() |
131 | { |
132 | QTest::addColumn<QString>(name: "font" ); |
133 | QTest::addColumn<bool>(name: "fixedPitch" ); |
134 | |
135 | QTest::newRow( dataTag: "Times New Roman" ) << QString( "Times New Roman" ) << false; |
136 | QTest::newRow( dataTag: "Arial" ) << QString( "Arial" ) << false; |
137 | QTest::newRow( dataTag: "Andale Mono" ) << QString( "Andale Mono" ) << true; |
138 | QTest::newRow( dataTag: "Courier" ) << QString( "Courier" ) << true; |
139 | QTest::newRow( dataTag: "Courier New" ) << QString( "Courier New" ) << true; |
140 | #ifndef Q_OS_MAC |
141 | QTest::newRow( dataTag: "Script" ) << QString( "Script" ) << false; |
142 | QTest::newRow( dataTag: "Lucida Console" ) << QString( "Lucida Console" ) << true; |
143 | QTest::newRow( dataTag: "DejaVu Sans" ) << QString( "DejaVu Sans" ) << false; |
144 | QTest::newRow( dataTag: "DejaVu Sans Mono" ) << QString( "DejaVu Sans Mono" ) << true; |
145 | #else |
146 | QTest::newRow( "Menlo" ) << QString( "Menlo" ) << true; |
147 | QTest::newRow( "Monaco" ) << QString( "Monaco" ) << true; |
148 | #endif |
149 | } |
150 | |
151 | void tst_QFontDatabase::fixedPitch() |
152 | { |
153 | QFETCH(QString, font); |
154 | QFETCH(bool, fixedPitch); |
155 | |
156 | QFontDatabase fdb; |
157 | if (!fdb.families().contains(str: font)) |
158 | QSKIP("Font not installed" ); |
159 | |
160 | QCOMPARE(fdb.isFixedPitch(font), fixedPitch); |
161 | |
162 | QFont qfont(font); |
163 | QFontInfo fi(qfont); |
164 | QCOMPARE(fi.fixedPitch(), fixedPitch); |
165 | } |
166 | |
167 | void tst_QFontDatabase::systemFixedFont() // QTBUG-54623 |
168 | { |
169 | QFont font = QFontDatabase::systemFont(type: QFontDatabase::FixedFont); |
170 | QFontInfo fontInfo(font); |
171 | bool fdbSaysFixed = QFontDatabase().isFixedPitch(family: fontInfo.family(), style: fontInfo.styleName()); |
172 | qCDebug(lcTests) << "system fixed font is" << font << "really fixed?" << fdbSaysFixed << fontInfo.fixedPitch(); |
173 | QVERIFY(fdbSaysFixed); |
174 | QVERIFY(fontInfo.fixedPitch()); |
175 | } |
176 | |
177 | #ifdef Q_OS_MAC |
178 | void tst_QFontDatabase::trickyFonts_data() |
179 | { |
180 | QTest::addColumn<QString>("font" ); |
181 | |
182 | QTest::newRow( "Geeza Pro" ) << QString( "Geeza Pro" ); |
183 | } |
184 | |
185 | void tst_QFontDatabase::trickyFonts() |
186 | { |
187 | QFETCH(QString, font); |
188 | |
189 | QFontDatabase fdb; |
190 | if (!fdb.families().contains(font)) |
191 | QSKIP( "Font not installed" ); |
192 | |
193 | QFont qfont(font); |
194 | QFontInfo fi(qfont); |
195 | QCOMPARE(fi.family(), font); |
196 | } |
197 | #endif |
198 | |
199 | void tst_QFontDatabase::widthTwoTimes_data() |
200 | { |
201 | QTest::addColumn<QString>(name: "font" ); |
202 | QTest::addColumn<int>(name: "pixelSize" ); |
203 | QTest::addColumn<QString>(name: "text" ); |
204 | |
205 | QTest::newRow(dataTag: "Arial" ) << QString("Arial" ) << 1000 << QString("Some text" ); |
206 | } |
207 | |
208 | void tst_QFontDatabase::widthTwoTimes() |
209 | { |
210 | QFETCH(QString, font); |
211 | QFETCH(int, pixelSize); |
212 | QFETCH(QString, text); |
213 | |
214 | QFont f; |
215 | f.setFamily(font); |
216 | f.setPixelSize(pixelSize); |
217 | |
218 | QFontMetrics fm(f); |
219 | int w1 = fm.horizontalAdvance(text, len: 0); |
220 | int w2 = fm.horizontalAdvance(text, len: 0); |
221 | |
222 | QCOMPARE(w1, w2); |
223 | } |
224 | |
225 | void tst_QFontDatabase::addAppFont_data() |
226 | { |
227 | QTest::addColumn<bool>(name: "useMemoryFont" ); |
228 | QTest::newRow(dataTag: "font file" ) << false; |
229 | QTest::newRow(dataTag: "memory font" ) << true; |
230 | } |
231 | |
232 | void tst_QFontDatabase::addAppFont() |
233 | { |
234 | QFETCH(bool, useMemoryFont); |
235 | QSignalSpy fontDbChangedSpy(QGuiApplication::instance(), SIGNAL(fontDatabaseChanged())); |
236 | |
237 | QFontDatabase db; |
238 | |
239 | const QStringList oldFamilies = db.families(); |
240 | QVERIFY(!oldFamilies.isEmpty()); |
241 | |
242 | fontDbChangedSpy.clear(); |
243 | |
244 | int id; |
245 | if (useMemoryFont) { |
246 | QFile fontfile(m_ledFont); |
247 | fontfile.open(flags: QIODevice::ReadOnly); |
248 | QByteArray fontdata = fontfile.readAll(); |
249 | QVERIFY(!fontdata.isEmpty()); |
250 | id = QFontDatabase::addApplicationFontFromData(fontData: fontdata); |
251 | } else { |
252 | id = QFontDatabase::addApplicationFont(fileName: m_ledFont); |
253 | } |
254 | #if defined(Q_OS_HPUX) && defined(QT_NO_FONTCONFIG) |
255 | // Documentation says that X11 systems that don't have fontconfig |
256 | // don't support application fonts. |
257 | QCOMPARE(id, -1); |
258 | return; |
259 | #endif |
260 | QCOMPARE(fontDbChangedSpy.count(), 1); |
261 | if (id == -1) |
262 | QSKIP("Skip the test since app fonts are not supported on this system" ); |
263 | |
264 | const QStringList addedFamilies = QFontDatabase::applicationFontFamilies(id); |
265 | QVERIFY(!addedFamilies.isEmpty()); |
266 | |
267 | const QStringList newFamilies = db.families(); |
268 | QVERIFY(!newFamilies.isEmpty()); |
269 | QVERIFY(newFamilies.count() >= oldFamilies.count()); |
270 | |
271 | for (int i = 0; i < addedFamilies.count(); ++i) { |
272 | QString family = addedFamilies.at(i); |
273 | QVERIFY(newFamilies.contains(family)); |
274 | QFont qfont(family); |
275 | QFontInfo fi(qfont); |
276 | QCOMPARE(fi.family(), family); |
277 | } |
278 | |
279 | QVERIFY(QFontDatabase::removeApplicationFont(id)); |
280 | QCOMPARE(fontDbChangedSpy.count(), 2); |
281 | |
282 | QVERIFY(db.families().count() <= oldFamilies.count()); |
283 | } |
284 | |
285 | void tst_QFontDatabase::addTwoAppFontsFromFamily() |
286 | { |
287 | int regularId = QFontDatabase::addApplicationFont(fileName: m_testFont); |
288 | if (regularId == -1) |
289 | QSKIP("Skip the test since app fonts are not supported on this system" ); |
290 | |
291 | int italicId = QFontDatabase::addApplicationFont(fileName: m_testFontItalic); |
292 | QVERIFY(italicId != -1); |
293 | |
294 | QVERIFY(!QFontDatabase::applicationFontFamilies(regularId).isEmpty()); |
295 | QVERIFY(!QFontDatabase::applicationFontFamilies(italicId).isEmpty()); |
296 | |
297 | QString regularFontName = QFontDatabase::applicationFontFamilies(id: regularId).first(); |
298 | QString italicFontName = QFontDatabase::applicationFontFamilies(id: italicId).first(); |
299 | QCOMPARE(regularFontName, italicFontName); |
300 | |
301 | QFont italicFont = QFontDatabase().font(family: italicFontName, |
302 | style: QString::fromLatin1(str: "Italic" ), pointSize: 14); |
303 | QVERIFY(italicFont.italic()); |
304 | |
305 | QFontDatabase::removeApplicationFont(id: regularId); |
306 | QFontDatabase::removeApplicationFont(id: italicId); |
307 | } |
308 | |
309 | void tst_QFontDatabase::aliases() |
310 | { |
311 | QFontDatabase db; |
312 | const QStringList families = db.families(); |
313 | QVERIFY(!families.isEmpty()); |
314 | QString firstFont; |
315 | for (int i = 0; i < families.size(); ++i) { |
316 | if (!families.at(i).contains(c: '[')) { |
317 | firstFont = families.at(i); |
318 | break; |
319 | } |
320 | } |
321 | |
322 | if (firstFont.isEmpty()) |
323 | QSKIP("Skipped because there are no unambiguous font families on the system." ); |
324 | |
325 | QVERIFY(db.hasFamily(firstFont)); |
326 | const QString alias = QStringLiteral("AliasToFirstFont" ) + firstFont; |
327 | QVERIFY(!db.hasFamily(alias)); |
328 | QPlatformFontDatabase::registerAliasToFontFamily(familyName: firstFont, alias); |
329 | QVERIFY(db.hasFamily(alias)); |
330 | } |
331 | |
332 | void tst_QFontDatabase::fallbackFonts() |
333 | { |
334 | QTextLayout layout; |
335 | QString s; |
336 | s.append(c: QChar(0x31)); |
337 | s.append(c: QChar(0x05D0)); |
338 | layout.setText(s); |
339 | layout.beginLayout(); |
340 | layout.createLine(); |
341 | layout.endLayout(); |
342 | |
343 | QList<QGlyphRun> runs = layout.glyphRuns(from: 0, length: 1); |
344 | foreach (QGlyphRun run, runs) { |
345 | QRawFont rawFont = run.rawFont(); |
346 | QVERIFY(rawFont.isValid()); |
347 | |
348 | QCOMPARE(run.glyphIndexes().size(), 1); |
349 | QVERIFY(run.glyphIndexes().at(0) != 0); |
350 | } |
351 | } |
352 | |
353 | static QString testString() |
354 | { |
355 | return QStringLiteral("foo bar" ); |
356 | } |
357 | |
358 | void tst_QFontDatabase::condensedFontWidthNoFontMerging() |
359 | { |
360 | int regularFontId = QFontDatabase::addApplicationFont(fileName: m_testFont); |
361 | int condensedFontId = QFontDatabase::addApplicationFont(fileName: m_testFontCondensed); |
362 | |
363 | QVERIFY(!QFontDatabase::applicationFontFamilies(regularFontId).isEmpty()); |
364 | QVERIFY(!QFontDatabase::applicationFontFamilies(condensedFontId).isEmpty()); |
365 | |
366 | QString regularFontName = QFontDatabase::applicationFontFamilies(id: regularFontId).first(); |
367 | QString condensedFontName = QFontDatabase::applicationFontFamilies(id: condensedFontId).first(); |
368 | |
369 | QFont condensedFont1(condensedFontName); |
370 | if (regularFontName == condensedFontName) |
371 | condensedFont1.setStyleName(QStringLiteral("Condensed" )); |
372 | condensedFont1.setStyleStrategy(QFont::PreferMatch); |
373 | |
374 | QFont condensedFont2 = condensedFont1; |
375 | condensedFont2.setStyleStrategy(QFont::StyleStrategy(QFont::NoFontMerging | QFont::PreferMatch)); |
376 | |
377 | QCOMPARE(QFontMetricsF(condensedFont2).horizontalAdvance(QStringLiteral("foobar" )), |
378 | QFontMetricsF(condensedFont1).horizontalAdvance(QStringLiteral("foobar" ))); |
379 | } |
380 | |
381 | void tst_QFontDatabase::condensedFontWidth() |
382 | { |
383 | QFontDatabase db; |
384 | QFontDatabase::addApplicationFont(fileName: m_testFont); |
385 | QFontDatabase::addApplicationFont(fileName: m_testFontCondensed); |
386 | |
387 | QVERIFY(db.hasFamily("QtBidiTestFont" )); |
388 | if (!db.hasFamily(family: "QtBidiTestFontCondensed" )) |
389 | QSKIP("This platform doesn't support font sub-family names (QTBUG-55625)" ); |
390 | |
391 | // Test we really get a condensed font, and a not renormalized one (QTBUG-48043): |
392 | QFont testFont("QtBidiTestFont" ); |
393 | QFont testFontCondensed("QtBidiTestFontCondensed" ); |
394 | QFontMetrics fmTF(testFont); |
395 | QFontMetrics fmTFC(testFontCondensed); |
396 | QVERIFY(fmTF.horizontalAdvance(testString()) > fmTFC.horizontalAdvance(testString())); |
397 | |
398 | } |
399 | |
400 | void tst_QFontDatabase::condensedFontMatching() |
401 | { |
402 | QFontDatabase db; |
403 | QFontDatabase::removeAllApplicationFonts(); |
404 | QFontDatabase::addApplicationFont(fileName: m_testFontCondensed); |
405 | if (!db.hasFamily(family: "QtBidiTestFont" )) |
406 | QSKIP("This platform doesn't support preferred font family names (QTBUG-53478)" ); |
407 | QFontDatabase::addApplicationFont(fileName: m_testFont); |
408 | |
409 | // Test we correctly get the condensed font using different font matching methods: |
410 | QFont tfcByStretch("QtBidiTestFont" ); |
411 | tfcByStretch.setStretch(QFont::Condensed); |
412 | QFont tfcByStyleName("QtBidiTestFont" ); |
413 | tfcByStyleName.setStyleName("Condensed" ); |
414 | |
415 | #ifdef Q_OS_WIN |
416 | QFont f; |
417 | f.setStyleStrategy(QFont::NoFontMerging); |
418 | QFontPrivate *font_d = QFontPrivate::get(f); |
419 | if (font_d->engineForScript(QChar::Script_Common)->type() != QFontEngine::Freetype) |
420 | QEXPECT_FAIL("" ,"No matching of sub-family by stretch on Windows" , Continue); |
421 | #endif |
422 | |
423 | #ifdef Q_OS_ANDROID |
424 | QEXPECT_FAIL("" , "QTBUG-69216" , Continue); |
425 | #endif |
426 | QCOMPARE(QFontMetrics(tfcByStretch).horizontalAdvance(testString()), |
427 | QFontMetrics(tfcByStyleName).horizontalAdvance(testString())); |
428 | |
429 | if (!db.hasFamily(family: "QtBidiTestFontCondensed" )) |
430 | QSKIP("This platform doesn't support font sub-family names (QTBUG-55625)" ); |
431 | |
432 | QFont tfcBySubfamilyName("QtBidiTestFontCondensed" ); |
433 | QCOMPARE(QFontMetrics(tfcByStyleName).horizontalAdvance(testString()), |
434 | QFontMetrics(tfcBySubfamilyName).horizontalAdvance(testString())); |
435 | } |
436 | |
437 | void tst_QFontDatabase::rasterFonts() |
438 | { |
439 | QFont font(QLatin1String("Fixedsys" ), 1000); |
440 | QFontInfo fontInfo(font); |
441 | |
442 | if (fontInfo.family() != font.family()) |
443 | QSKIP("Fixedsys font not available." ); |
444 | |
445 | QVERIFY(!QFontDatabase().isSmoothlyScalable(font.family())); |
446 | QVERIFY(fontInfo.pointSize() != font.pointSize()); |
447 | } |
448 | |
449 | void tst_QFontDatabase::smoothFonts() |
450 | { |
451 | QFont font(QLatin1String("Arial" ), 1000); |
452 | QFontInfo fontInfo(font); |
453 | |
454 | if (fontInfo.family() != font.family()) |
455 | QSKIP("Arial font not available." ); |
456 | |
457 | // Smooth and bitmap scaling are mutually exclusive |
458 | QVERIFY(QFontDatabase().isSmoothlyScalable(font.family())); |
459 | QVERIFY(!QFontDatabase().isBitmapScalable(font.family())); |
460 | } |
461 | |
462 | void tst_QFontDatabase::registerOpenTypePreferredNamesSystem() |
463 | { |
464 | QFontDatabase db; |
465 | // This font family was picked because it was the only one I had installed which showcased the |
466 | // problem |
467 | if (!db.hasFamily(family: QString::fromLatin1(str: "Source Code Pro ExtraLight" ))) |
468 | QSKIP("Source Code Pro ExtraLight is not installed" ); |
469 | |
470 | QStringList styles = db.styles(family: QString::fromLatin1(str: "Source Code Pro" )); |
471 | QVERIFY(styles.contains(QLatin1String("ExtraLight" ))); |
472 | } |
473 | |
474 | void tst_QFontDatabase::registerOpenTypePreferredNamesApplication() |
475 | { |
476 | QFontDatabase db; |
477 | |
478 | int id = QFontDatabase::addApplicationFont(fileName: QString::fromLatin1(str: ":/testfont_open.otf" )); |
479 | if (id == -1) |
480 | QSKIP("Skip the test since app fonts are not supported on this system" ); |
481 | |
482 | QStringList styles = db.styles(family: QString::fromLatin1(str: "QtBidiTestFont" )); |
483 | QVERIFY(styles.contains(QLatin1String("Open" ))); |
484 | |
485 | QFontDatabase::removeApplicationFont(id); |
486 | } |
487 | |
488 | QTEST_MAIN(tst_QFontDatabase) |
489 | #include "tst_qfontdatabase.moc" |
490 | |