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 | |
30 | #include <QtTest/QtTest> |
31 | #include <qfont.h> |
32 | #include <qfontmetrics.h> |
33 | #include <qfontdatabase.h> |
34 | #include <private/qfontengine_p.h> |
35 | #include <qstringlist.h> |
36 | #include <qlist.h> |
37 | |
38 | class tst_QFontMetrics : public QObject |
39 | { |
40 | Q_OBJECT |
41 | |
42 | private slots: |
43 | void same(); |
44 | void metrics(); |
45 | void boundingRect(); |
46 | void elidedText_data(); |
47 | void elidedText(); |
48 | void veryNarrowElidedText(); |
49 | void averageCharWidth(); |
50 | |
51 | #if QT_DEPRECATED_SINCE(5, 11) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
52 | void bypassShaping(); |
53 | #endif |
54 | |
55 | void elidedMultiLength(); |
56 | void elidedMultiLengthF(); |
57 | void inFontUcs4(); |
58 | void lineWidth(); |
59 | void mnemonicTextWidth(); |
60 | void leadingBelowLine(); |
61 | void elidedMetrics(); |
62 | void zeroWidthMetrics(); |
63 | }; |
64 | |
65 | void tst_QFontMetrics::same() |
66 | { |
67 | QFont font; |
68 | font.setBold(true); |
69 | QFontMetrics fm(font); |
70 | const QString text = QLatin1String("Some stupid STRING" ); |
71 | QCOMPARE(fm.size(0, text), fm.size(0, text)) ; |
72 | |
73 | for (int i = 10; i <= 32; ++i) { |
74 | font.setPixelSize(i); |
75 | QFontMetrics fm1(font); |
76 | QCOMPARE(fm1.size(0, text), fm1.size(0, text)); |
77 | } |
78 | |
79 | { |
80 | QImage image; |
81 | QFontMetrics fm2(font, &image); |
82 | QString text2 = QLatin1String("Foo Foo" ); |
83 | QCOMPARE(fm2.size(0, text2), fm2.size(0, text2)); //used to crash |
84 | } |
85 | |
86 | { |
87 | QImage image; |
88 | QFontMetricsF fm3(font, &image); |
89 | QString text2 = QLatin1String("Foo Foo" ); |
90 | QCOMPARE(fm3.size(0, text2), fm3.size(0, text2)); //used to crash |
91 | } |
92 | } |
93 | |
94 | |
95 | void tst_QFontMetrics::metrics() |
96 | { |
97 | QFont font; |
98 | QFontDatabase fdb; |
99 | |
100 | // Query the QFontDatabase for a specific font, store the |
101 | // result in family, style and size. |
102 | QStringList families = fdb.families(); |
103 | if (families.isEmpty()) |
104 | return; |
105 | |
106 | QStringList::ConstIterator f_it, f_end = families.end(); |
107 | for (f_it = families.begin(); f_it != f_end; ++f_it) { |
108 | const QString &family = *f_it; |
109 | |
110 | QStringList styles = fdb.styles(family); |
111 | QStringList::ConstIterator s_it, s_end = styles.end(); |
112 | for (s_it = styles.begin(); s_it != s_end; ++s_it) { |
113 | const QString &style = *s_it; |
114 | |
115 | if (fdb.isSmoothlyScalable(family, style)) { |
116 | // smoothly scalable font... don't need to load every pointsize |
117 | font = fdb.font(family, style, pointSize: 12); |
118 | |
119 | QFontMetrics fontmetrics(font); |
120 | QCOMPARE(fontmetrics.ascent() + fontmetrics.descent(), |
121 | fontmetrics.height()); |
122 | |
123 | QCOMPARE(fontmetrics.height() + fontmetrics.leading(), |
124 | fontmetrics.lineSpacing()); |
125 | } else { |
126 | QList<int> sizes = fdb.pointSizes(family, style); |
127 | QVERIFY(!sizes.isEmpty()); |
128 | QList<int>::ConstIterator z_it, z_end = sizes.end(); |
129 | for (z_it = sizes.begin(); z_it != z_end; ++z_it) { |
130 | const int size = *z_it; |
131 | |
132 | // Initialize the font, and check if it is an exact match |
133 | font = fdb.font(family, style, pointSize: size); |
134 | |
135 | QFontMetrics fontmetrics(font); |
136 | QCOMPARE(fontmetrics.ascent() + fontmetrics.descent(), |
137 | fontmetrics.height()); |
138 | QCOMPARE(fontmetrics.height() + fontmetrics.leading(), |
139 | fontmetrics.lineSpacing()); |
140 | } |
141 | } |
142 | } |
143 | } |
144 | } |
145 | |
146 | void tst_QFontMetrics::boundingRect() |
147 | { |
148 | QFont f; |
149 | f.setPointSize(24); |
150 | QFontMetrics fm(f); |
151 | QRect r = fm.boundingRect(QChar('Y')); |
152 | QVERIFY(r.top() < 0); |
153 | r = fm.boundingRect(text: QString("Y" )); |
154 | QVERIFY(r.top() < 0); |
155 | } |
156 | |
157 | void tst_QFontMetrics::elidedText_data() |
158 | { |
159 | QTest::addColumn<QFont>(name: "font" ); |
160 | QTest::addColumn<QString>(name: "text" ); |
161 | |
162 | QTest::newRow(dataTag: "helvetica hello" ) << QFont("helvetica" ,10) << QString("hello" ) ; |
163 | QTest::newRow(dataTag: "helvetica hello &Bye" ) << QFont("helvetica" ,10) << QString("hello&Bye" ) ; |
164 | } |
165 | |
166 | |
167 | void tst_QFontMetrics::elidedText() |
168 | { |
169 | QFETCH(QFont, font); |
170 | QFETCH(QString, text); |
171 | QFontMetrics fm(font); |
172 | int w = fm.horizontalAdvance(text); |
173 | QString newtext = fm.elidedText(text,mode: Qt::ElideRight,width: w+1, flags: 0); |
174 | QCOMPARE(text,newtext); // should not elide |
175 | newtext = fm.elidedText(text,mode: Qt::ElideRight,width: w-1, flags: 0); |
176 | QVERIFY(text != newtext); // should elide |
177 | } |
178 | |
179 | void tst_QFontMetrics::veryNarrowElidedText() |
180 | { |
181 | QFont f; |
182 | QFontMetrics fm(f); |
183 | QString text("hello" ); |
184 | QCOMPARE(fm.elidedText(text, Qt::ElideRight, 0), QString()); |
185 | } |
186 | |
187 | void tst_QFontMetrics::averageCharWidth() |
188 | { |
189 | QFont f; |
190 | QFontMetrics fm(f); |
191 | QVERIFY(fm.averageCharWidth() != 0); |
192 | QFontMetricsF fmf(f); |
193 | QVERIFY(fmf.averageCharWidth() != 0); |
194 | } |
195 | |
196 | #if QT_DEPRECATED_SINCE(5, 11) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
197 | void tst_QFontMetrics::bypassShaping() |
198 | { |
199 | QFont f; |
200 | f.setStyleStrategy(QFont::ForceIntegerMetrics); |
201 | QFontMetrics fm(f); |
202 | QString text = " A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z" ; |
203 | int textWidth = fm.width(text, len: -1, flags: Qt::TextBypassShaping); |
204 | QVERIFY(textWidth != 0); |
205 | int charsWidth = 0; |
206 | for (int i = 0; i < text.size(); ++i) |
207 | charsWidth += fm.horizontalAdvance(text[i]); |
208 | // This assertion is needed in Qt WebKit's WebCore::Font::offsetForPositionForSimpleText |
209 | QCOMPARE(textWidth, charsWidth); |
210 | } |
211 | #endif |
212 | |
213 | template<class FontMetrics, typename PrimitiveType> void elidedMultiLength_helper() |
214 | { |
215 | QString text1 = QLatin1String("Long Text 1\x9cShorter\x9csmall" ); |
216 | QString text1_long = "Long Text 1" ; |
217 | QString text1_short = "Shorter" ; |
218 | QString text1_small = "small" ; |
219 | FontMetrics fm = FontMetrics(QFont()); |
220 | PrimitiveType width_long = fm.size(0, text1_long).width(); |
221 | QCOMPARE(fm.elidedText(text1,Qt::ElideRight, 8000), text1_long); |
222 | QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_long + 1), text1_long); |
223 | QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_long - 1), text1_short); |
224 | PrimitiveType width_short = fm.size(0, text1_short).width(); |
225 | QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_short + 1), text1_short); |
226 | QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_short - 1), text1_small); |
227 | |
228 | // Not even wide enough for "small" - should use ellipsis |
229 | QChar ellipsisChar(0x2026); |
230 | QString text1_el = QString::fromLatin1(str: "s" ) + ellipsisChar; |
231 | PrimitiveType width_small = fm.horizontalAdvance(text1_el); |
232 | QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_small + 1), text1_el); |
233 | } |
234 | |
235 | void tst_QFontMetrics::elidedMultiLength() |
236 | { |
237 | elidedMultiLength_helper<QFontMetrics, int>(); |
238 | } |
239 | |
240 | void tst_QFontMetrics::elidedMultiLengthF() |
241 | { |
242 | elidedMultiLength_helper<QFontMetricsF, qreal>(); |
243 | } |
244 | |
245 | void tst_QFontMetrics::inFontUcs4() |
246 | { |
247 | int id = QFontDatabase::addApplicationFont(fileName: ":/fonts/ucs4font.ttf" ); |
248 | QVERIFY(id >= 0); |
249 | |
250 | QFont font("QtTestUcs4" ); |
251 | { |
252 | QFontMetrics fm(font); |
253 | QVERIFY(fm.inFontUcs4(0x1D7FF)); |
254 | } |
255 | |
256 | { |
257 | QFontMetricsF fm(font); |
258 | QVERIFY(fm.inFontUcs4(0x1D7FF)); |
259 | } |
260 | |
261 | { |
262 | QFontEngine *engine = QFontPrivate::get(font)->engineForScript(script: QChar::Script_Common); |
263 | QGlyphLayout glyphs; |
264 | glyphs.numGlyphs = 3; |
265 | uint buf[3]; |
266 | glyphs.glyphs = buf; |
267 | |
268 | QString string; |
269 | { |
270 | string.append(c: QChar::highSurrogate(ucs4: 0x1D7FF)); |
271 | string.append(c: QChar::lowSurrogate(ucs4: 0x1D7FF)); |
272 | |
273 | glyphs.numGlyphs = 3; |
274 | glyphs.glyphs[0] = 0; |
275 | QVERIFY(engine->stringToCMap(string.constData(), string.size(), |
276 | &glyphs, &glyphs.numGlyphs, |
277 | QFontEngine::GlyphIndicesOnly)); |
278 | QCOMPARE(glyphs.numGlyphs, 1); |
279 | QCOMPARE(glyphs.glyphs[0], uint(1)); |
280 | } |
281 | { |
282 | string.clear(); |
283 | string.append(c: QChar::ObjectReplacementCharacter); |
284 | |
285 | glyphs.numGlyphs = 3; |
286 | glyphs.glyphs[0] = 0; |
287 | QVERIFY(engine->stringToCMap(string.constData(), string.size(), |
288 | &glyphs, &glyphs.numGlyphs, |
289 | QFontEngine::GlyphIndicesOnly)); |
290 | QVERIFY(glyphs.glyphs[0] != 1); |
291 | } |
292 | } |
293 | |
294 | QFontDatabase::removeApplicationFont(id); |
295 | } |
296 | |
297 | void tst_QFontMetrics::lineWidth() |
298 | { |
299 | // QTBUG-13009, QTBUG-13011 |
300 | QFont smallFont; |
301 | smallFont.setPointSize(8); |
302 | smallFont.setWeight(QFont::Light); |
303 | const QFontMetrics smallFontMetrics(smallFont); |
304 | |
305 | QFont bigFont; |
306 | bigFont.setPointSize(40); |
307 | bigFont.setWeight(QFont::Black); |
308 | const QFontMetrics bigFontMetrics(bigFont); |
309 | |
310 | QVERIFY(smallFontMetrics.lineWidth() >= 1); |
311 | QVERIFY(smallFontMetrics.lineWidth() < bigFontMetrics.lineWidth()); |
312 | } |
313 | |
314 | void tst_QFontMetrics::mnemonicTextWidth() |
315 | { |
316 | // QTBUG-41593 |
317 | QFont f; |
318 | QFontMetrics fm(f); |
319 | const QString f1 = "File" ; |
320 | const QString f2 = "&File" ; |
321 | |
322 | QCOMPARE(fm.size(Qt::TextShowMnemonic, f1), fm.size(Qt::TextShowMnemonic, f2)); |
323 | QCOMPARE(fm.size(Qt::TextHideMnemonic, f1), fm.size(Qt::TextHideMnemonic, f2)); |
324 | } |
325 | |
326 | void tst_QFontMetrics::leadingBelowLine() |
327 | { |
328 | QScriptLine line; |
329 | line.leading = 10; |
330 | line.leadingIncluded = true; |
331 | line.ascent = 5; |
332 | QCOMPARE(line.height(), line.ascent + line.descent + line.leading); |
333 | QCOMPARE(line.base(), line.ascent); |
334 | } |
335 | |
336 | void tst_QFontMetrics::elidedMetrics() |
337 | { |
338 | QString testFont = QFINDTESTDATA("fonts/testfont.ttf" ); |
339 | QVERIFY(!testFont.isEmpty()); |
340 | |
341 | int id = QFontDatabase::addApplicationFont(fileName: testFont); |
342 | QVERIFY(id >= 0); |
343 | |
344 | QFont font(QFontDatabase::applicationFontFamilies(id).at(i: 0)); |
345 | font.setPixelSize(12.0); |
346 | |
347 | QFontMetricsF metrics(font); |
348 | QString s = QStringLiteral("VeryLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongText" ); |
349 | |
350 | QRectF boundingRect = metrics.boundingRect(string: s); |
351 | |
352 | QString elided = metrics.elidedText(text: s, mode: Qt::ElideRight, width: boundingRect.width() / 2.0); |
353 | |
354 | QRectF elidedBoundingRect = metrics.boundingRect(string: elided); |
355 | |
356 | QCOMPARE(boundingRect.height(), elidedBoundingRect.height()); |
357 | QCOMPARE(boundingRect.y(), elidedBoundingRect.y()); |
358 | |
359 | QFontDatabase::removeApplicationFont(id); |
360 | } |
361 | |
362 | void tst_QFontMetrics::zeroWidthMetrics() |
363 | { |
364 | QString zwnj(QChar(0x200c)); |
365 | QString zwsp(QChar(0x200b)); |
366 | |
367 | QFont font; |
368 | QFontMetricsF fm(font); |
369 | QCOMPARE(fm.horizontalAdvance(zwnj), 0); |
370 | QCOMPARE(fm.horizontalAdvance(zwsp), 0); |
371 | QCOMPARE(fm.boundingRect(zwnj).width(), 0); |
372 | QCOMPARE(fm.boundingRect(zwsp).width(), 0); |
373 | |
374 | QString string1 = QStringLiteral("(" ) + zwnj + QStringLiteral(")" ); |
375 | QString string2 = QStringLiteral("(" ) + zwnj + zwnj + QStringLiteral(")" ); |
376 | QString string3 = QStringLiteral("(" ) + zwsp + QStringLiteral(")" ); |
377 | QString string4 = QStringLiteral("(" ) + zwsp + zwsp + QStringLiteral(")" ); |
378 | |
379 | QCOMPARE(fm.horizontalAdvance(string1), fm.horizontalAdvance(string2)); |
380 | QCOMPARE(fm.horizontalAdvance(string3), fm.horizontalAdvance(string4)); |
381 | QCOMPARE(fm.boundingRect(string1).width(), fm.boundingRect(string2).width()); |
382 | QCOMPARE(fm.boundingRect(string3).width(), fm.boundingRect(string4).width()); |
383 | } |
384 | |
385 | QTEST_MAIN(tst_QFontMetrics) |
386 | #include "tst_qfontmetrics.moc" |
387 | |