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/*
31 !!!!!! Warning !!!!!
32 Please don't save this file in emacs. It contains utf8 text sequences emacs will
33 silently convert to a series of question marks.
34 */
35#include <QtTest/QtTest>
36
37
38
39#include <private/qtextengine_p.h>
40#include <qtextlayout.h>
41
42#include <qdebug.h>
43
44
45#define TESTFONT_SIZE 12
46
47Q_DECLARE_METATYPE(QTextOption::WrapMode)
48Q_DECLARE_METATYPE(Qt::LayoutDirection)
49Q_DECLARE_METATYPE(Qt::AlignmentFlag)
50
51class tst_QTextLayout : public QObject
52{
53 Q_OBJECT
54
55public:
56 tst_QTextLayout();
57
58private slots:
59 void init();
60 void cleanup();
61 void getSetCheck();
62 void lineBreaking();
63#ifdef QT_BUILD_INTERNAL
64 void simpleBoundingRect();
65 void threeLineBoundingRect_data();
66 void threeLineBoundingRect();
67 void boundingRectWithLongLineAndNoWrap();
68 void forcedBreaks();
69 void breakAny();
70 void noWrap();
71 void cursorToXForInlineObjects();
72 void cursorToXForSetColumns();
73 void cursorToXForTrailingSpaces_data();
74 void cursorToXForTrailingSpaces();
75 void cursorToXInvalidInput();
76 void horizontalAlignment_data();
77 void horizontalAlignment();
78 void horizontalAlignmentMultiline_data();
79 void horizontalAlignmentMultiline();
80#endif
81 void defaultWordSeparators_data();
82 void defaultWordSeparators();
83 void cursorMovementFromInvalidPositions();
84 void cursorMovementInsideSpaces();
85 void charWordStopOnLineSeparator();
86#ifdef QT_BUILD_INTERNAL
87 void xToCursorAtEndOfLine();
88#endif
89 void boundingRectTopLeft();
90 void graphemeBoundaryForSurrogatePairs();
91 void tabStops();
92 void integerOverflow();
93#ifdef QT_BUILD_INTERNAL
94 void testDefaultTabs();
95 void testTabs();
96 void testMultilineTab();
97 void testRightTab();
98 void testTabsInAlignedParag();
99 void testCenteredTab();
100 void testDelimiterTab();
101 void testMultiTab();
102 void testTabDPIScale();
103 void tabsForRtl();
104#endif
105 void tabHeight();
106 void capitalization_allUpperCase();
107 void capitalization_allUpperCase_newline();
108 void capitalization_allLowerCase();
109 void capitalization_smallCaps();
110 void capitalization_capitalize();
111 void longText();
112 void widthOfTabs();
113 void columnWrapWithTabs();
114 void boundingRectForUnsetLineWidth();
115 void boundingRectForSetLineWidth();
116 void glyphLessItems();
117 void justifyTrailingSpaces();
118 void layoutWithCustomTabStops();
119
120 // QTextLine stuff
121#ifdef QT_BUILD_INTERNAL
122 void setNumColumnsWrapAtWordBoundaryOrAnywhere();
123 void setNumColumnsWordWrap();
124 void smallTextLengthNoWrap();
125 void smallTextLengthWordWrap();
126 void smallTextLengthWrapAtWordBoundaryOrAnywhere();
127 void testLineBreakingAllSpaces();
128#endif
129 void lineWidthFromBOM();
130 void textWidthVsWIdth();
131 void textWithSurrogates_qtbug15679();
132 void textWidthWithStackedTextEngine();
133 void textWidthWithLineSeparator();
134 void cursorInLigatureWithMultipleLines();
135 void xToCursorForLigatures();
136 void cursorInNonStopChars();
137 void nbsp();
138 void nbspWithFormat();
139 void noModificationOfInputString();
140 void superscriptCrash_qtbug53911();
141 void showLineAndParagraphSeparatorsCrash();
142 void koreanWordWrap();
143 void tooManyDirectionalCharctersCrash_qtbug77819();
144 void softHyphens_data();
145 void softHyphens();
146 void min_maximumWidth();
147
148private:
149 QFont testFont;
150};
151
152// Testing get/set functions
153void tst_QTextLayout::getSetCheck()
154{
155 QString str("Bogus text");
156 QTextLayout layout(str);
157 layout.beginLayout();
158 QTextEngine *engine = layout.engine();
159 QTextInlineObject obj1(0, engine);
160 // qreal QTextInlineObject::width()
161 // void QTextInlineObject::setWidth(qreal)
162 obj1.setWidth(0.0);
163 QCOMPARE(0.0, obj1.width());
164 obj1.setWidth(1.2);
165 QVERIFY(1.0 < obj1.width());
166
167 // qreal QTextInlineObject::ascent()
168 // void QTextInlineObject::setAscent(qreal)
169 obj1.setAscent(0.0);
170 QCOMPARE(0.0, obj1.ascent());
171 obj1.setAscent(1.2);
172 QVERIFY(1.0 < obj1.ascent());
173
174 // qreal QTextInlineObject::descent()
175 // void QTextInlineObject::setDescent(qreal)
176 obj1.setDescent(0.0);
177 QCOMPARE(0.0, obj1.descent());
178 obj1.setDescent(1.2);
179 QVERIFY(1.0 < obj1.descent());
180
181 QTextLayout obj2;
182 // bool QTextLayout::cacheEnabled()
183 // void QTextLayout::setCacheEnabled(bool)
184 obj2.setCacheEnabled(false);
185 QCOMPARE(false, obj2.cacheEnabled());
186 obj2.setCacheEnabled(true);
187 QCOMPARE(true, obj2.cacheEnabled());
188}
189
190#ifdef QT_BUILD_INTERNAL
191QT_BEGIN_NAMESPACE
192// qfontdatabase.cpp
193Q_AUTOTEST_EXPORT void qt_setQtEnableTestFont(bool value);
194QT_END_NAMESPACE
195#endif
196
197tst_QTextLayout::tst_QTextLayout()
198{
199#ifdef QT_BUILD_INTERNAL
200 qt_setQtEnableTestFont(value: true);
201#endif
202}
203
204void tst_QTextLayout::init()
205{
206 testFont = QFont();
207#ifdef QT_BUILD_INTERNAL
208 testFont.setFamily("__Qt__Box__Engine__");
209#endif
210 testFont.setPixelSize(TESTFONT_SIZE);
211 testFont.setWeight(QFont::Normal);
212#ifdef QT_BUILD_INTERNAL
213 QCOMPARE(QFontMetrics(testFont).horizontalAdvance('a'), testFont.pixelSize());
214#endif
215}
216
217void tst_QTextLayout::cleanup()
218{
219 testFont = QFont();
220}
221
222void tst_QTextLayout::lineBreaking()
223{
224#if 0
225 struct Breaks {
226 const char *utf8;
227 uchar breaks[32];
228 };
229 Breaks brks[] = {
230 { "11", { false, 0xff } },
231 { "aa", { false, 0xff } },
232 { "++", { false, 0xff } },
233 { "--", { false, 0xff } },
234 { "((", { false, 0xff } },
235 { "))", { false, 0xff } },
236 { "..", { false, 0xff } },
237 { "\"\"", { false, 0xff } },
238 { "$$", { false, 0xff } },
239 { "!!", { false, 0xff } },
240 { "??", { false, 0xff } },
241 { ",,", { false, 0xff } },
242
243 { ")()", { true, false, 0xff } },
244 { "?!?", { false, false, 0xff } },
245 { ".,.", { false, false, 0xff } },
246 { "+-+", { false, false, 0xff } },
247 { "+=+", { false, false, 0xff } },
248 { "+(+", { false, false, 0xff } },
249 { "+)+", { false, false, 0xff } },
250
251 { "a b", { false, true, 0xff } },
252 { "a(b", { false, false, 0xff } },
253 { "a)b", { false, false, 0xff } },
254 { "a-b", { false, true, 0xff } },
255 { "a.b", { false, false, 0xff } },
256 { "a+b", { false, false, 0xff } },
257 { "a?b", { false, false, 0xff } },
258 { "a!b", { false, false, 0xff } },
259 { "a$b", { false, false, 0xff } },
260 { "a,b", { false, false, 0xff } },
261 { "a/b", { false, false, 0xff } },
262 { "1/2", { false, false, 0xff } },
263 { "./.", { false, false, 0xff } },
264 { ",/,", { false, false, 0xff } },
265 { "!/!", { false, false, 0xff } },
266 { "\\/\\", { false, false, 0xff } },
267 { "1 2", { false, true, 0xff } },
268 { "1(2", { false, false, 0xff } },
269 { "1)2", { false, false, 0xff } },
270 { "1-2", { false, false, 0xff } },
271 { "1.2", { false, false, 0xff } },
272 { "1+2", { false, false, 0xff } },
273 { "1?2", { false, true, 0xff } },
274 { "1!2", { false, true, 0xff } },
275 { "1$2", { false, false, 0xff } },
276 { "1,2", { false, false, 0xff } },
277 { "1/2", { false, false, 0xff } },
278 { "\330\260\331\216\331\204\331\220\331\203\331\216", { false, false, false, false, false, 0xff } },
279 { "\330\247\331\204\331\205 \330\247\331\204\331\205", { false, false, false, true, false, false, 0xff } },
280 { "1#2", { false, false, 0xff } },
281 { "!#!", { false, false, 0xff } },
282 { 0, {} }
283 };
284 Breaks *b = brks;
285 while (b->utf8) {
286 QString str = QString::fromUtf8(b->utf8);
287 QTextEngine engine(str, QFont());
288 const QCharAttributes *attrs = engine.attributes();
289 QVERIFY(!attrs[0].lineBreak);
290 int i;
291 for (i = 0; i < (int)str.length() - 1; ++i) {
292 QVERIFY(b->breaks[i] != 0xff);
293 if ( attrs[i + 1].lineBreak != (bool)b->breaks[i] ) {
294 qDebug("test case \"%s\" failed at char %d; break type: %d", b->utf8, i, attrs[i + 1].lineBreak);
295 QCOMPARE( attrs[i + 1].lineBreak, (bool)b->breaks[i] );
296 }
297 }
298 QCOMPARE(b->breaks[i], (uchar)0xff);
299 ++b;
300 }
301#endif
302}
303
304#ifdef QT_BUILD_INTERNAL
305void tst_QTextLayout::simpleBoundingRect()
306{
307 /* just check if boundingRect() gives sane values. The text is not broken. */
308
309 QString hello("hello world");
310
311 const int width = hello.length() * testFont.pixelSize();
312
313 QTextLayout layout(hello, testFont);
314 layout.beginLayout();
315
316 QTextLine line = layout.createLine();
317 line.setLineWidth(width);
318 QCOMPARE(line.textLength(), hello.length());
319 QCOMPARE(layout.boundingRect(), QRectF(0, 0, width, QFontMetrics(testFont).height()));
320}
321
322void tst_QTextLayout::threeLineBoundingRect_data()
323{
324 QTest::addColumn<QChar>(name: "wordBoundary1");
325 QTest::addColumn<QChar>(name: "wordBoundary2");
326 QTest::newRow(dataTag: "2x' '") << QChar(' ') << QChar(' ');
327 QTest::newRow(dataTag: "2x'\\n'") << QChar('\n') << QChar('\n');
328 QTest::newRow(dataTag: "' ' + '\\n'") << QChar(' ') << QChar('\n');
329 QTest::newRow(dataTag: "'\\n' + ' '") << QChar('\n') << QChar(' ');
330 QTest::newRow(dataTag: "2x'\\t'") << QChar('\t') << QChar('\t');
331 QTest::newRow(dataTag: "2xsoft hyphen") << QChar(0xad) << QChar(0xad);
332 QTest::newRow(dataTag: "2x'-'") << QChar('-') << QChar('-');
333 QTest::newRow(dataTag: "2x'/'") << QChar('/') << QChar('/');
334 QTest::newRow(dataTag: "soft hyphen + ' '") << QChar(0xad) << QChar(' ');
335 QTest::newRow(dataTag: "soft hyphen + '\\n'") << QChar(0xad) << QChar('\n');
336 QTest::newRow(dataTag: "soft hyphen + '-'") << QChar(0xad) << QChar('-');
337 QTest::newRow(dataTag: "' ' + soft hyphen") << QChar(' ') << QChar(0xad);
338 QTest::newRow(dataTag: "'\\n' + soft hyphen") << QChar('\n') << QChar(0xad);
339 QTest::newRow(dataTag: "'-' + soft hyphen") << QChar('-') << QChar(0xad);
340}
341
342void tst_QTextLayout::threeLineBoundingRect()
343{
344 /* stricter check. break text into three lines */
345 QFETCH(QChar, wordBoundary1);
346 QFETCH(QChar, wordBoundary2);
347
348 QString firstWord("hello");
349 QString secondWord("test");
350 QString thirdWord("world");
351 QString text(firstWord + wordBoundary1 + secondWord + wordBoundary2 + thirdWord);
352
353 int firstLineWidth = firstWord.length() * testFont.pixelSize();
354 int secondLineWidth = secondWord.length() * testFont.pixelSize();
355 int thirdLineWidth = thirdWord.length() * testFont.pixelSize();
356 // Trailing spaces do not count to line width:
357 if (!wordBoundary1.isSpace())
358 firstLineWidth += testFont.pixelSize();
359 if (!wordBoundary2.isSpace())
360 secondLineWidth += testFont.pixelSize();
361 // But trailing spaces do count to line length:
362 const int firstLineLength = firstWord.length() + 1;
363 const int secondLineLength = secondWord.length() + 1;
364 const int thirdLineLength = thirdWord.length();
365
366 const int longestLine = qMax(a: firstLineWidth, b: qMax(a: secondLineWidth, b: thirdLineWidth));
367
368 QTextLayout layout(text, testFont);
369 layout.beginLayout();
370
371 int pos = 0;
372 int y = 0;
373 QTextLine line = layout.createLine();
374 line.setLineWidth(firstLineWidth);
375 line.setPosition(QPoint(0, y));
376 QCOMPARE(line.textStart(), pos);
377 QCOMPARE(line.textLength(), firstLineLength);
378 QCOMPARE(qRound(line.naturalTextWidth()), firstLineWidth);
379
380 pos += line.textLength();
381 y += qRound(d: line.ascent() + line.descent());
382
383 line = layout.createLine();
384 line.setLineWidth(secondLineWidth);
385 line.setPosition(QPoint(0, y));
386 QCOMPARE(line.textStart(), pos);
387 QCOMPARE(line.textLength(), secondLineLength);
388 QCOMPARE(qRound(line.naturalTextWidth()), secondLineWidth);
389
390 pos += line.textLength();
391 y += qRound(d: line.ascent() + line.descent());
392
393 line = layout.createLine();
394 line.setLineWidth(secondLineWidth);
395 line.setPosition(QPoint(0, y));
396 QCOMPARE(line.textStart(), pos);
397 QCOMPARE(line.textLength(), thirdLineLength);
398 QCOMPARE(qRound(line.naturalTextWidth()), thirdLineWidth);
399 y += qRound(d: line.ascent() + line.descent());
400
401 QCOMPARE(layout.boundingRect(), QRectF(0, 0, longestLine, y));
402}
403
404void tst_QTextLayout::boundingRectWithLongLineAndNoWrap()
405{
406 QString longString("thisisaverylongstringthatcannotbewrappedatallitjustgoesonandonlikeonebigword");
407
408 const int width = longString.length() * testFont.pixelSize() / 20; // very small widthx
409
410 QTextLayout layout(longString, testFont);
411 layout.beginLayout();
412 QTextLine line = layout.createLine();
413 line.setLineWidth(width);
414
415 QVERIFY(layout.boundingRect().width() >= line.width());
416 QCOMPARE(layout.boundingRect().width(), line.naturalTextWidth());
417}
418
419void tst_QTextLayout::forcedBreaks()
420{
421 QString text = "A\n\nB\nC";
422 text.replace(before: '\n', after: QChar::LineSeparator);
423
424 QTextLayout layout(text, testFont);
425
426 layout.beginLayout();
427
428 int pos = 0;
429
430 QTextLine line = layout.createLine();
431 line.setLineWidth(0x10000);
432 QCOMPARE(line.textStart(), pos);
433 QCOMPARE(line.textLength(),2);
434 QCOMPARE(qRound(line.naturalTextWidth()),testFont.pixelSize());
435 QCOMPARE((int) line.height(), testFont.pixelSize());
436 QCOMPARE(line.xToCursor(0), line.textStart());
437 pos += line.textLength();
438
439 line = layout.createLine();
440 line.setLineWidth(0x10000);
441 QCOMPARE(line.textStart(),pos);
442 QCOMPARE(line.textLength(),1);
443 QCOMPARE(qRound(line.naturalTextWidth()), 0);
444 QCOMPARE((int) line.height(), testFont.pixelSize());
445 QCOMPARE(line.xToCursor(0), line.textStart());
446 pos += line.textLength();
447
448 line = layout.createLine();
449 line.setLineWidth(0x10000);
450 QCOMPARE(line.textStart(),pos);
451 QCOMPARE(line.textLength(),2);
452 QCOMPARE(qRound(line.naturalTextWidth()),testFont.pixelSize());
453 QCOMPARE(qRound(line.height()), testFont.pixelSize());
454 QCOMPARE(line.xToCursor(0), line.textStart());
455 pos += line.textLength();
456
457 line = layout.createLine();
458 line.setLineWidth(0x10000);
459 QCOMPARE(line.textStart(),pos);
460 QCOMPARE(line.textLength(),1);
461 QCOMPARE(qRound(line.naturalTextWidth()), testFont.pixelSize());
462 QCOMPARE((int) line.height(), testFont.pixelSize());
463 QCOMPARE(line.xToCursor(0), line.textStart());
464
465 layout.endLayout();
466}
467
468void tst_QTextLayout::breakAny()
469{
470 QString text = "ABCD";
471
472 QTextLayout layout(text, testFont);
473 layout.setCacheEnabled(true);
474 QTextLine line;
475
476 QTextOption opt;
477 opt.setWrapMode(QTextOption::WrapAnywhere);
478 layout.setTextOption(opt);
479 layout.beginLayout();
480
481 line = layout.createLine();
482 line.setLineWidth(testFont.pixelSize() * 2);
483 QCOMPARE(line.textStart(), 0);
484 QCOMPARE(line.textLength(), 2);
485
486 line = layout.createLine();
487 line.setLineWidth(testFont.pixelSize() * 2);
488 QCOMPARE(line.textStart(), 2);
489 QCOMPARE(line.textLength(), 2);
490
491 line = layout.createLine();
492 QVERIFY(!line.isValid());
493
494 layout.endLayout();
495
496 text = "ABCD EFGH";
497 layout.setText(text);
498 layout.beginLayout();
499
500 line = layout.createLine();
501 line.setLineWidth(testFont.pixelSize() * 7);
502 QCOMPARE(line.textStart(), 0);
503 QCOMPARE(line.textLength(), 7);
504
505 layout.endLayout();
506}
507
508void tst_QTextLayout::noWrap()
509{
510 QString text = "AB CD";
511
512 QTextLayout layout(text, testFont);
513 QTextLine line;
514
515 QTextOption opt;
516 opt.setWrapMode(QTextOption::NoWrap);
517 layout.setTextOption(opt);
518 layout.beginLayout();
519
520 line = layout.createLine();
521 line.setLineWidth(testFont.pixelSize() * 2);
522 QCOMPARE(line.textStart(), 0);
523 QCOMPARE(line.textLength(), 5);
524
525 line = layout.createLine();
526 QVERIFY(!line.isValid());
527
528 layout.endLayout();
529}
530
531void tst_QTextLayout::cursorToXForInlineObjects()
532{
533 QChar ch(QChar::ObjectReplacementCharacter);
534 QString text(ch);
535 QTextLayout layout(text, testFont);
536 layout.beginLayout();
537
538 QTextEngine *engine = layout.engine();
539 const int item = engine->findItem(strPos: 0);
540 engine->layoutData->items[item].width = 32;
541
542 QTextLine line = layout.createLine();
543 line.setLineWidth(0x10000);
544
545 QCOMPARE(line.cursorToX(0), qreal(0));
546 QCOMPARE(line.cursorToX(1), qreal(32));
547}
548
549void tst_QTextLayout::cursorToXForSetColumns()
550{
551 QTextLayout lay("abc", testFont);
552 lay.setCacheEnabled(true);
553 QTextOption o = lay.textOption();
554 o.setWrapMode(QTextOption::WrapAnywhere);
555
556 // enable/disable this line for full effect ;)
557 o.setAlignment(Qt::AlignHCenter);
558
559 lay.setTextOption(o);
560 lay.beginLayout();
561 QTextLine line = lay.createLine();
562 line.setNumColumns(1);
563 lay.endLayout();
564 QCOMPARE(line.cursorToX(0), 0.);
565 QCOMPARE(line.cursorToX(1), (qreal) TESTFONT_SIZE);
566}
567
568void tst_QTextLayout::cursorToXForTrailingSpaces_data()
569{
570 qreal width = TESTFONT_SIZE * 4;
571
572 QTest::addColumn<QTextOption::WrapMode>(name: "wrapMode");
573 QTest::addColumn<Qt::LayoutDirection>(name: "textDirection");
574 QTest::addColumn<Qt::AlignmentFlag>(name: "alignment");
575 QTest::addColumn<qreal>(name: "cursorAt0");
576 QTest::addColumn<qreal>(name: "cursorAt4");
577 QTest::addColumn<qreal>(name: "cursorAt6");
578
579 // Aligned left from start of visible characters.
580 QTest::newRow(dataTag: "ltr nowrap lalign")
581 << QTextOption::NoWrap
582 << Qt::LeftToRight
583 << Qt::AlignLeft
584 << qreal(0)
585 << width
586 << qreal(TESTFONT_SIZE * 6);
587
588 // Aligned left from start of visible characters.
589 QTest::newRow(dataTag: "ltr wrap lalign")
590 << QTextOption::WrapAnywhere
591 << Qt::LeftToRight
592 << Qt::AlignLeft
593 << qreal(0)
594 << width
595 << width;
596
597 // Aligned right from end of whitespace characters.
598 QTest::newRow(dataTag: "ltr nowrap ralign")
599 << QTextOption::NoWrap
600 << Qt::LeftToRight
601 << Qt::AlignRight
602 << qreal(TESTFONT_SIZE * -2)
603 << qreal(TESTFONT_SIZE * 2)
604 << width;
605
606 // Aligned right from end of visible characters.
607 QTest::newRow(dataTag: "ltr wrap ralign")
608 << QTextOption::WrapAnywhere
609 << Qt::LeftToRight
610 << Qt::AlignRight
611 << qreal(TESTFONT_SIZE)
612 << width
613 << width;
614
615 // Aligned center of all characters
616 QTest::newRow(dataTag: "ltr nowrap calign")
617 << QTextOption::NoWrap
618 << Qt::LeftToRight
619 << Qt::AlignHCenter
620 << qreal(TESTFONT_SIZE * -1)
621 << qreal(TESTFONT_SIZE * 3)
622 << qreal(TESTFONT_SIZE * 5);
623
624 // Aligned center of visible characters
625 QTest::newRow(dataTag: "ltr wrap calign")
626 << QTextOption::WrapAnywhere
627 << Qt::LeftToRight
628 << Qt::AlignHCenter
629 << qreal(TESTFONT_SIZE * 0.5)
630 << qreal(width)
631 << qreal(width);
632
633 // Aligned right from start of visible characters
634 QTest::newRow(dataTag: "rtl nowrap ralign")
635 << QTextOption::NoWrap
636 << Qt::RightToLeft
637 << Qt::AlignRight
638 << width
639 << qreal(0)
640 << qreal(TESTFONT_SIZE * -2);
641
642 // Aligned right from start of visible characters
643 QTest::newRow(dataTag: "rtl wrap ralign")
644 << QTextOption::WrapAnywhere
645 << Qt::RightToLeft
646 << Qt::AlignRight
647 << width
648 << qreal(0)
649 << qreal(0);
650
651 // Aligned left from end of whitespace characters
652 QTest::newRow(dataTag: "rtl nowrap lalign")
653 << QTextOption::NoWrap
654 << Qt::RightToLeft
655 << Qt::AlignLeft
656 << qreal(TESTFONT_SIZE * 6)
657 << qreal(TESTFONT_SIZE * 2)
658 << qreal(0);
659
660 // Aligned left from end of visible characters
661 QTest::newRow(dataTag: "rtl wrap lalign")
662 << QTextOption::WrapAnywhere
663 << Qt::RightToLeft
664 << Qt::AlignLeft
665 << qreal(TESTFONT_SIZE * 3)
666 << qreal(0)
667 << qreal(0);
668
669 // Aligned center of all characters
670 QTest::newRow(dataTag: "rtl nowrap calign")
671 << QTextOption::NoWrap
672 << Qt::RightToLeft
673 << Qt::AlignHCenter
674 << qreal(TESTFONT_SIZE * 5)
675 << qreal(TESTFONT_SIZE * 1)
676 << qreal(TESTFONT_SIZE * -1);
677
678 // Aligned center of visible characters
679 QTest::newRow(dataTag: "rtl wrap calign")
680 << QTextOption::WrapAnywhere
681 << Qt::RightToLeft
682 << Qt::AlignHCenter
683 << qreal(TESTFONT_SIZE * 3.5)
684 << qreal(0)
685 << qreal(0);
686}
687
688void tst_QTextLayout::cursorToXForTrailingSpaces()
689{
690 QFETCH(QTextOption::WrapMode, wrapMode);
691 QFETCH(Qt::LayoutDirection, textDirection);
692 QFETCH(Qt::AlignmentFlag, alignment);
693 QFETCH(qreal, cursorAt0);
694 QFETCH(qreal, cursorAt4);
695 QFETCH(qreal, cursorAt6);
696
697 QTextLayout layout("%^& ", testFont);
698
699 QTextOption o = layout.textOption();
700 o.setTextDirection(textDirection);
701 o.setAlignment(alignment);
702 o.setWrapMode(wrapMode);
703 layout.setTextOption(o);
704
705 layout.beginLayout();
706 QTextLine line = layout.createLine();
707 line.setLineWidth(TESTFONT_SIZE * 4);
708 layout.endLayout();
709
710 QCOMPARE(line.cursorToX(0), cursorAt0);
711 QCOMPARE(line.cursorToX(4), cursorAt4);
712 QCOMPARE(line.cursorToX(6), cursorAt6);
713}
714
715void tst_QTextLayout::cursorToXInvalidInput()
716{
717 QTextLayout layout("aaa", testFont);
718
719 layout.beginLayout();
720 QTextLine line = layout.createLine();
721 line.setLineWidth(5);
722 layout.endLayout();
723
724 int cursorPos;
725
726 cursorPos = 0;
727 layout.lineAt(i: 0).cursorToX(cursorPos: &cursorPos);
728 QCOMPARE(cursorPos, 0);
729 cursorPos = -300;
730 layout.lineAt(i: 0).cursorToX(cursorPos: &cursorPos);
731 QCOMPARE(cursorPos, 0);
732 cursorPos = 300;
733 layout.lineAt(i: 0).cursorToX(cursorPos: &cursorPos);
734 QCOMPARE(cursorPos, 3);
735}
736
737void tst_QTextLayout::horizontalAlignment_data()
738{
739 qreal width = TESTFONT_SIZE * 4;
740
741 QTest::addColumn<QTextOption::WrapMode>(name: "wrapMode");
742 QTest::addColumn<Qt::LayoutDirection>(name: "textDirection");
743 QTest::addColumn<Qt::AlignmentFlag>(name: "alignment");
744 QTest::addColumn<qreal>(name: "naturalLeft");
745 QTest::addColumn<qreal>(name: "naturalRight");
746
747 // Aligned left from start of visible characters.
748 QTest::newRow(dataTag: "ltr nowrap lalign")
749 << QTextOption::NoWrap
750 << Qt::LeftToRight
751 << Qt::AlignLeft
752 << qreal(0)
753 << qreal(TESTFONT_SIZE * 6);
754
755 // Aligned left from start of visible characters.
756 QTest::newRow(dataTag: "ltr wrap lalign")
757 << QTextOption::WrapAnywhere
758 << Qt::LeftToRight
759 << Qt::AlignLeft
760 << qreal(0)
761 << qreal(TESTFONT_SIZE * 3);
762
763 // Aligned right from end of whitespace characters.
764 QTest::newRow(dataTag: "ltr nowrap ralign")
765 << QTextOption::NoWrap
766 << Qt::LeftToRight
767 << Qt::AlignRight
768 << qreal(TESTFONT_SIZE * - 2)
769 << width;
770
771 // Aligned right from end of visible characters.
772 QTest::newRow(dataTag: "ltr wrap ralign")
773 << QTextOption::WrapAnywhere
774 << Qt::LeftToRight
775 << Qt::AlignRight
776 << qreal(TESTFONT_SIZE)
777 << width;
778
779 // Aligned center of all characters
780 QTest::newRow(dataTag: "ltr nowrap calign")
781 << QTextOption::NoWrap
782 << Qt::LeftToRight
783 << Qt::AlignHCenter
784 << qreal(TESTFONT_SIZE * -1)
785 << qreal(TESTFONT_SIZE * 5);
786
787 // Aligned center of visible characters
788 QTest::newRow(dataTag: "ltr wrap calign")
789 << QTextOption::WrapAnywhere
790 << Qt::LeftToRight
791 << Qt::AlignHCenter
792 << qreal(TESTFONT_SIZE * 0.5)
793 << qreal(TESTFONT_SIZE * 3.5);
794
795 // Aligned right from start of visible characters
796 QTest::newRow(dataTag: "rtl nowrap ralign")
797 << QTextOption::NoWrap
798 << Qt::RightToLeft
799 << Qt::AlignRight
800 << qreal(TESTFONT_SIZE * -2)
801 << width;
802
803 // Aligned right from start of visible characters
804 QTest::newRow(dataTag: "rtl wrap ralign")
805 << QTextOption::WrapAnywhere
806 << Qt::RightToLeft
807 << Qt::AlignRight
808 << qreal(TESTFONT_SIZE * 1)
809 << width;
810
811 // Aligned left from end of whitespace characters
812 QTest::newRow(dataTag: "rtl nowrap lalign")
813 << QTextOption::NoWrap
814 << Qt::RightToLeft
815 << Qt::AlignLeft
816 << qreal(0)
817 << qreal(TESTFONT_SIZE * 6);
818
819 // Aligned left from end of visible characters
820 QTest::newRow(dataTag: "rtl wrap lalign")
821 << QTextOption::WrapAnywhere
822 << Qt::RightToLeft
823 << Qt::AlignLeft
824 << qreal(0)
825 << qreal(TESTFONT_SIZE * 3);
826
827 // Aligned center of all characters
828 QTest::newRow(dataTag: "rtl nowrap calign")
829 << QTextOption::NoWrap
830 << Qt::RightToLeft
831 << Qt::AlignHCenter
832 << qreal(TESTFONT_SIZE * -1)
833 << qreal(TESTFONT_SIZE * 5);
834
835 // Aligned center of visible characters
836 QTest::newRow(dataTag: "rtl wrap calign")
837 << QTextOption::WrapAnywhere
838 << Qt::RightToLeft
839 << Qt::AlignHCenter
840 << qreal(TESTFONT_SIZE * 0.5)
841 << qreal(TESTFONT_SIZE * 3.5);
842}
843
844void tst_QTextLayout::horizontalAlignment()
845{
846 QFETCH(QTextOption::WrapMode, wrapMode);
847 QFETCH(Qt::LayoutDirection, textDirection);
848 QFETCH(Qt::AlignmentFlag, alignment);
849 QFETCH(qreal, naturalLeft);
850 QFETCH(qreal, naturalRight);
851
852 QTextLayout layout("%^& ", testFont);
853
854 QTextOption o = layout.textOption();
855 o.setTextDirection(textDirection);
856 o.setAlignment(alignment);
857 o.setWrapMode(wrapMode);
858 layout.setTextOption(o);
859
860 layout.beginLayout();
861 QTextLine line = layout.createLine();
862 line.setLineWidth(TESTFONT_SIZE * 4);
863 layout.endLayout();
864
865 QRectF naturalRect = line.naturalTextRect();
866 QCOMPARE(naturalRect.left(), naturalLeft);
867 QCOMPARE(naturalRect.right(), naturalRight);
868}
869
870
871void tst_QTextLayout::horizontalAlignmentMultiline_data()
872{
873 qreal width = TESTFONT_SIZE * 8;
874
875 const QString linebreakText = QStringLiteral("^%$&") + QChar(0x2028) + QStringLiteral("^%&*^$");
876 QString wrappingText("^%$&^%&*^$");
877 QString wrappingWhitespaceText("^%$& ^%&*^$");
878
879 QTest::addColumn<QString>(name: "text");
880 QTest::addColumn<Qt::LayoutDirection>(name: "textDirection");
881 QTest::addColumn<Qt::AlignmentFlag>(name: "alignment");
882 QTest::addColumn<qreal>(name: "firstLeft");
883 QTest::addColumn<qreal>(name: "firstRight");
884 QTest::addColumn<qreal>(name: "lastLeft");
885 QTest::addColumn<qreal>(name: "lastRight");
886
887 Qt::LayoutDirection textDirection[] = { Qt::LeftToRight, Qt::RightToLeft };
888 QByteArray textDirectionText [] = { "ltr ", "rtl " };
889 for (int i = 0; i < 2; ++i) {
890 // Aligned left from start of visible characters.
891 QTest::newRow(dataTag: textDirectionText[i] + "linebreak lalign")
892 << linebreakText
893 << textDirection[i]
894 << Qt::AlignLeft
895 << qreal(0)
896 << qreal(TESTFONT_SIZE * 4)
897 << qreal(0)
898 << qreal(TESTFONT_SIZE * 6);
899
900 // Aligned left from start of visible characters.
901 QTest::newRow(dataTag: textDirectionText[i] + "wrap-text lalign")
902 << wrappingText
903 << textDirection[i]
904 << Qt::AlignLeft
905 << qreal(0)
906 << width
907 << qreal(0)
908 << qreal(TESTFONT_SIZE * 2);
909
910 // Aligned left from start of visible characters.
911 QTest::newRow(dataTag: textDirectionText[i] + "wrap-ws lalign")
912 << wrappingWhitespaceText
913 << textDirection[i]
914 << Qt::AlignLeft
915 << qreal(0)
916 << qreal(TESTFONT_SIZE * 4)
917 << qreal(0)
918 << qreal(TESTFONT_SIZE * 6);
919
920 // Aligned right from start of visible characters.
921 QTest::newRow(dataTag: textDirectionText[i] + "linebreak ralign")
922 << linebreakText
923 << textDirection[i]
924 << Qt::AlignRight
925 << qreal(TESTFONT_SIZE * 4)
926 << width
927 << qreal(TESTFONT_SIZE * 2)
928 << width;
929
930 // Aligned right from start of visible characters.
931 QTest::newRow(dataTag: textDirectionText[i] + "wrap-text ralign")
932 << wrappingText
933 << textDirection[i]
934 << Qt::AlignRight
935 << qreal(0)
936 << width
937 << qreal(TESTFONT_SIZE * 6)
938 << width;
939
940 // Aligned left from start of visible characters.
941 QTest::newRow(dataTag: textDirectionText[i] + "wrap-ws ralign")
942 << wrappingWhitespaceText
943 << textDirection[i]
944 << Qt::AlignRight
945 << qreal(TESTFONT_SIZE * 4)
946 << width
947 << qreal(TESTFONT_SIZE * 2)
948 << width;
949
950 // Aligned center from start of visible characters.
951 QTest::newRow(dataTag: textDirectionText[i] + "linebreak calign")
952 << linebreakText
953 << textDirection[i]
954 << Qt::AlignCenter
955 << qreal(TESTFONT_SIZE * 2)
956 << qreal(TESTFONT_SIZE * 6)
957 << qreal(TESTFONT_SIZE * 1)
958 << qreal(TESTFONT_SIZE * 7);
959
960 // Aligned center from start of visible characters.
961 QTest::newRow(dataTag: textDirectionText[i] + "wrap-text calign")
962 << wrappingText
963 << textDirection[i]
964 << Qt::AlignCenter
965 << qreal(0)
966 << width
967 << qreal(TESTFONT_SIZE * 3)
968 << qreal(TESTFONT_SIZE * 5);
969
970 // Aligned center from start of visible characters.
971 QTest::newRow(dataTag: textDirectionText[i] + "wrap-ws calign")
972 << wrappingWhitespaceText
973 << textDirection[i]
974 << Qt::AlignCenter
975 << qreal(TESTFONT_SIZE * 2)
976 << qreal(TESTFONT_SIZE * 6)
977 << qreal(TESTFONT_SIZE * 1)
978 << qreal(TESTFONT_SIZE * 7);
979 }
980}
981
982void tst_QTextLayout::horizontalAlignmentMultiline()
983{
984 QFETCH(QString, text);
985 QFETCH(Qt::LayoutDirection, textDirection);
986 QFETCH(Qt::AlignmentFlag, alignment);
987 QFETCH(qreal, firstLeft);
988 QFETCH(qreal, firstRight);
989 QFETCH(qreal, lastLeft);
990 QFETCH(qreal, lastRight);
991
992 QTextLayout layout(text, testFont);
993
994 QTextOption o = layout.textOption();
995 o.setTextDirection(textDirection);
996 o.setAlignment(alignment);
997 o.setWrapMode(QTextOption::WrapAnywhere);
998 layout.setTextOption(o);
999
1000 layout.beginLayout();
1001 QTextLine firstLine = layout.createLine();
1002 QTextLine lastLine;
1003 for (QTextLine line = firstLine; line.isValid(); line = layout.createLine()) {
1004 line.setLineWidth(TESTFONT_SIZE * 8);
1005 lastLine = line;
1006 }
1007 layout.endLayout();
1008
1009 qDebug() << firstLine.textLength() << firstLine.naturalTextRect() << lastLine.naturalTextRect();
1010
1011 QRectF rect = firstLine.naturalTextRect();
1012 QCOMPARE(rect.left(), firstLeft);
1013 QCOMPARE(rect.right(), firstRight);
1014
1015 rect = lastLine.naturalTextRect();
1016 QCOMPARE(rect.left(), lastLeft);
1017 QCOMPARE(rect.right(), lastRight);
1018}
1019#endif
1020
1021void tst_QTextLayout::defaultWordSeparators_data()
1022{
1023 QTest::addColumn<QString>(name: "text");
1024 QTest::addColumn<int>(name: "startPos");
1025 QTest::addColumn<int>(name: "endPos");
1026
1027 QString separators(".,:;-<>[](){}=/+%&^*");
1028 separators += QLatin1String("!?");
1029 for (int i = 0; i < separators.count(); ++i) {
1030 QTest::newRow(dataTag: QString::number(i).toLatin1().data())
1031 << QString::fromLatin1(str: "abcd") + separators.at(i) + QString::fromLatin1(str: "efgh")
1032 << 0 << 4;
1033 }
1034
1035 QTest::newRow(dataTag: "nbsp")
1036 << QString::fromLatin1(str: "abcd") + QString(QChar::Nbsp) + QString::fromLatin1(str: "efgh")
1037 << 0 << 5;
1038
1039 QTest::newRow(dataTag: "tab")
1040 << QString::fromLatin1(str: "abcd") + QString::fromLatin1(str: "\t") + QString::fromLatin1(str: "efgh")
1041 << 0 << 5;
1042
1043 QTest::newRow(dataTag: "lineseparator")
1044 << QString::fromLatin1(str: "abcd") + QString(QChar::LineSeparator) + QString::fromLatin1(str: "efgh")
1045 << 0 << 5;
1046
1047 QTest::newRow(dataTag: "empty")
1048 << QString()
1049 << 0 << 0;
1050}
1051
1052void tst_QTextLayout::defaultWordSeparators()
1053{
1054 QFETCH(QString, text);
1055 QFETCH(int, startPos);
1056 QFETCH(int, endPos);
1057 QTextLayout layout(text);
1058
1059 QCOMPARE(layout.nextCursorPosition(startPos, QTextLayout::SkipWords), endPos);
1060 QCOMPARE(layout.previousCursorPosition(endPos, QTextLayout::SkipWords), startPos);
1061}
1062
1063void tst_QTextLayout::cursorMovementFromInvalidPositions()
1064{
1065 int badpos = 10000;
1066
1067 QTextLayout layout("ABC");
1068
1069 QCOMPARE(layout.previousCursorPosition(-badpos, QTextLayout::SkipCharacters), -badpos);
1070 QCOMPARE(layout.nextCursorPosition(-badpos, QTextLayout::SkipCharacters), -badpos);
1071
1072 QCOMPARE(layout.previousCursorPosition(badpos, QTextLayout::SkipCharacters), badpos);
1073 QCOMPARE(layout.nextCursorPosition(badpos, QTextLayout::SkipCharacters), badpos);
1074}
1075
1076void tst_QTextLayout::cursorMovementInsideSpaces()
1077{
1078 QTextLayout layout("ABC DEF");
1079
1080 QCOMPARE(layout.previousCursorPosition(6, QTextLayout::SkipWords), 0);
1081 QCOMPARE(layout.nextCursorPosition(6, QTextLayout::SkipWords), 15);
1082
1083
1084 QTextLayout layout2("ABC\t\t\t\t\t\t\t\t\t\t\t\tDEF");
1085
1086 QCOMPARE(layout2.previousCursorPosition(6, QTextLayout::SkipWords), 0);
1087 QCOMPARE(layout2.nextCursorPosition(6, QTextLayout::SkipWords), 15);
1088}
1089
1090void tst_QTextLayout::charWordStopOnLineSeparator()
1091{
1092 const QChar lineSeparator(QChar::LineSeparator);
1093 QString txt;
1094 txt.append(c: lineSeparator);
1095 txt.append(c: lineSeparator);
1096 QTextLayout layout(txt);
1097 QTextEngine *engine = layout.engine();
1098 const QCharAttributes *attrs = engine->attributes();
1099 QVERIFY(attrs);
1100 QVERIFY(attrs[1].graphemeBoundary);
1101}
1102
1103#ifdef QT_BUILD_INTERNAL
1104void tst_QTextLayout::xToCursorAtEndOfLine()
1105{
1106 QString text = "FirstLine SecondLine";
1107 text.replace(before: '\n', after: QChar::LineSeparator);
1108
1109 const qreal firstLineWidth = QString("FirstLine").length() * testFont.pixelSize();
1110
1111 QTextLayout layout(text, testFont);
1112 layout.setCacheEnabled(true);
1113
1114 layout.beginLayout();
1115 QTextLine line = layout.createLine();
1116 QVERIFY(line.isValid());
1117 line.setLineWidth(firstLineWidth);
1118 QVERIFY(layout.createLine().isValid());
1119 QVERIFY(!layout.createLine().isValid());
1120 layout.endLayout();
1121
1122 line = layout.lineAt(i: 0);
1123 QCOMPARE(line.xToCursor(100000), 9);
1124 line = layout.lineAt(i: 1);
1125 QCOMPARE(line.xToCursor(100000), 20);
1126}
1127#endif
1128
1129void tst_QTextLayout::boundingRectTopLeft()
1130{
1131 QString text = "FirstLine\nSecondLine";
1132 text.replace(before: '\n', after: QChar::LineSeparator);
1133
1134 QTextLayout layout(text);
1135 layout.setCacheEnabled(true);
1136
1137 layout.beginLayout();
1138 QTextLine firstLine = layout.createLine();
1139 QVERIFY(firstLine.isValid());
1140 firstLine.setPosition(QPointF(10, 10));
1141 QTextLine secondLine = layout.createLine();
1142 QVERIFY(secondLine.isValid());
1143 secondLine.setPosition(QPointF(20, 20));
1144 layout.endLayout();
1145
1146 QCOMPARE(layout.boundingRect().topLeft(), firstLine.position());
1147}
1148
1149void tst_QTextLayout::graphemeBoundaryForSurrogatePairs()
1150{
1151 QString txt;
1152 txt.append(c: QLatin1Char('a'));
1153 txt.append(c: 0xd87e);
1154 txt.append(c: 0xdc25);
1155 txt.append(c: QLatin1Char('b'));
1156 QTextLayout layout(txt);
1157 QTextEngine *engine = layout.engine();
1158 const QCharAttributes *attrs = engine->attributes();
1159 QVERIFY(attrs);
1160 QVERIFY(attrs[0].graphemeBoundary);
1161 QVERIFY(attrs[1].graphemeBoundary);
1162 QVERIFY(!attrs[2].graphemeBoundary);
1163 QVERIFY(attrs[3].graphemeBoundary);
1164}
1165
1166void tst_QTextLayout::tabStops()
1167{
1168 QString txt("Hello there\tworld");
1169 QTextLayout layout(txt);
1170 layout.beginLayout();
1171 QTextLine line = layout.createLine();
1172
1173 QVERIFY(line.isValid());
1174 line.setNumColumns(11);
1175 QCOMPARE(line.textLength(), 12);
1176
1177 line = layout.createLine();
1178 QVERIFY(line.isValid());
1179 line.setNumColumns(5);
1180 QCOMPARE(line.textLength(), 5);
1181
1182 layout.endLayout();
1183}
1184
1185void tst_QTextLayout::integerOverflow()
1186{
1187 QString txt("Hello world... ");
1188
1189 for (int i = 0; i < 8; ++i)
1190 txt += txt;
1191
1192 QTextLayout layout(txt);
1193 layout.beginLayout();
1194 QTextLine line = layout.createLine();
1195
1196 QVERIFY(line.isValid());
1197 line.setLineWidth(INT_MAX);
1198 QCOMPARE(line.textLength(), txt.length());
1199
1200 QVERIFY(!layout.createLine().isValid());
1201
1202 layout.endLayout();
1203}
1204
1205#ifdef QT_BUILD_INTERNAL
1206void tst_QTextLayout::setNumColumnsWrapAtWordBoundaryOrAnywhere()
1207{
1208 QString txt("This is a small test text");
1209 QTextLayout layout(txt, testFont);
1210 layout.setCacheEnabled(true);
1211 QTextOption option = layout.textOption();
1212 option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
1213 layout.setTextOption(option);
1214
1215 layout.beginLayout();
1216 QTextLine line1 = layout.createLine();
1217 QVERIFY(line1.isValid());
1218 line1.setNumColumns(1);
1219
1220 // qDebug() << line1.naturalTextWidth();
1221 QCOMPARE(line1.textLength(), 1);
1222 QVERIFY(line1.naturalTextWidth() == testFont.pixelSize()); // contains only one character
1223
1224 QTextLine line2 = layout.createLine();
1225 QVERIFY(line2.isValid());
1226
1227 layout.endLayout();
1228}
1229
1230void tst_QTextLayout::setNumColumnsWordWrap()
1231{
1232 QString txt("This is a small test text");
1233 QTextLayout layout(txt, testFont);
1234 layout.setCacheEnabled(true);
1235 QTextOption option = layout.textOption();
1236 option.setWrapMode(QTextOption::WordWrap);
1237 layout.setTextOption(option);
1238
1239 layout.beginLayout();
1240 QTextLine line1 = layout.createLine();
1241 QVERIFY(line1.isValid());
1242 line1.setNumColumns(1);
1243
1244 // qDebug() << line1.naturalTextWidth();
1245 QCOMPARE(line1.textLength(), 5);
1246 QVERIFY(line1.naturalTextWidth() > 20.0); // contains the whole first word.
1247
1248 QTextLine line2 = layout.createLine();
1249 QVERIFY(line2.isValid());
1250
1251 layout.endLayout();
1252}
1253
1254void tst_QTextLayout::smallTextLengthNoWrap()
1255{
1256 QString txt("This is a small test text");
1257 QTextLayout layout(txt, testFont);
1258 layout.setCacheEnabled(true);
1259 QTextOption option = layout.textOption();
1260 option.setWrapMode(QTextOption::NoWrap);
1261 layout.setTextOption(option);
1262
1263 /// NoWrap
1264 layout.beginLayout();
1265 QTextLine line1 = layout.createLine();
1266 QVERIFY(line1.isValid());
1267 line1.setLineWidth(5); // most certainly too short for the word 'This' to fit.
1268
1269 QCOMPARE(line1.width(), 5.0);
1270 QVERIFY(line1.naturalTextWidth() > 70); // contains all the text.
1271
1272 QTextLine line2 = layout.createLine();
1273 QVERIFY(! line2.isValid());
1274
1275 layout.endLayout();
1276}
1277
1278void tst_QTextLayout::smallTextLengthWordWrap()
1279{
1280 QString txt("This is a small test text");
1281 QTextLayout layout(txt, testFont);
1282 layout.setCacheEnabled(true);
1283 QTextOption option = layout.textOption();
1284 option.setWrapMode(QTextOption::WordWrap);
1285 layout.setTextOption(option);
1286
1287 /// WordWrap
1288 layout.beginLayout();
1289 QTextLine line1 = layout.createLine();
1290 QVERIFY(line1.isValid());
1291 line1.setLineWidth(5); // most certainly too short for the word 'This' to fit.
1292
1293 QCOMPARE(line1.width(), 5.0);
1294 QVERIFY(line1.naturalTextWidth() > 20.0); // contains the whole first word.
1295 QCOMPARE(line1.textLength(), 5);
1296
1297 QTextLine line2 = layout.createLine();
1298 QVERIFY(line2.isValid());
1299
1300 layout.endLayout();
1301}
1302
1303void tst_QTextLayout::smallTextLengthWrapAtWordBoundaryOrAnywhere()
1304{
1305 QString txt("This is a small test text");
1306 QTextLayout layout(txt, testFont);
1307 layout.setCacheEnabled(true);
1308 QTextOption option = layout.textOption();
1309 option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
1310 layout.setTextOption(option);
1311
1312 layout.beginLayout();
1313 QTextLine line1 = layout.createLine();
1314 QVERIFY(line1.isValid());
1315 line1.setLineWidth(5); // most certainly too short for the word 'This' to fit.
1316
1317 QCOMPARE(line1.width(), 5.0);
1318 // qDebug() << line1.naturalTextWidth();
1319 QCOMPARE(line1.textLength(), 1);
1320 QVERIFY(line1.naturalTextWidth() == testFont.pixelSize()); // contains just the characters that fit.
1321
1322 QTextLine line2 = layout.createLine();
1323 QVERIFY(line2.isValid());
1324
1325 layout.endLayout();
1326}
1327
1328void tst_QTextLayout::testDefaultTabs()
1329{
1330 QTextLayout layout("Foo\tBar\ta slightly longer text\tend.", testFont);
1331
1332 QFont font = layout.font();
1333 QFontPrivate *fd = QFontPrivate::get(font);
1334 qreal dpiScale = qreal(fd->dpi) / qreal(qt_defaultDpiY());
1335 if (!qFuzzyCompare(p1: dpiScale, p2: 1.0))
1336 QSKIP("Test logic does not work when tabs are scaled by dpi");
1337
1338 layout.setCacheEnabled(true);
1339 layout.beginLayout();
1340 QTextLine line = layout.createLine();
1341 line.setLineWidth(1000);
1342 layout.endLayout();
1343
1344 //qDebug() << "After the tab: " << line.cursorToX(4);
1345 QCOMPARE(line.cursorToX(4), 80.); // default tab is 80
1346 QCOMPARE(line.cursorToX(8), 160.);
1347 QCOMPARE(line.cursorToX(31), 480.);
1348
1349 QTextOption option = layout.textOption();
1350 option.setTabStopDistance(90);
1351 layout.setTextOption(option);
1352 layout.beginLayout();
1353 line = layout.createLine();
1354 line.setLineWidth(1000);
1355 layout.endLayout();
1356
1357 QCOMPARE(line.cursorToX(4), 90.);
1358 QCOMPARE(line.cursorToX(8), 180.);
1359 QCOMPARE(line.cursorToX(31), 450.);
1360
1361 QList<QTextOption::Tab> tabs;
1362 QTextOption::Tab tab;
1363 tab.position = 110; // set one tab to 110, but since the rest is unset they will be at the normal interval again.
1364 tabs.append(t: tab);
1365 option.setTabs(tabs);
1366 layout.setTextOption(option);
1367 layout.beginLayout();
1368 line = layout.createLine();
1369 line.setLineWidth(1000);
1370 layout.endLayout();
1371
1372 QCOMPARE(line.cursorToX(4), 110.);
1373 QCOMPARE(line.cursorToX(8), 180.);
1374 QCOMPARE(line.cursorToX(31), 450.);
1375}
1376
1377void tst_QTextLayout::testTabs()
1378{
1379 QTextLayout layout("Foo\tBar.", testFont);
1380
1381 QFont font = layout.font();
1382 QFontPrivate *fd = QFontPrivate::get(font);
1383 qreal dpiScale = qreal(fd->dpi) / qreal(qt_defaultDpiY());
1384 if (!qFuzzyCompare(p1: dpiScale, p2: 1.0))
1385 QSKIP("Test logic does not work when tabs are scaled by dpi");
1386
1387 layout.setCacheEnabled(true);
1388 QTextOption option = layout.textOption();
1389 option.setTabStopDistance(150);
1390 layout.setTextOption(option);
1391
1392 layout.beginLayout();
1393 QTextLine line = layout.createLine();
1394 line.setLineWidth(200.);
1395 layout.endLayout();
1396
1397 QVERIFY(line.naturalTextWidth() > 150);
1398 QCOMPARE(line.cursorToX(4), 150.);
1399}
1400
1401void tst_QTextLayout::testMultilineTab()
1402{
1403 QTextLayout layout("Lorem ipsum dolor sit\tBar.", testFont);
1404
1405 QFont font = layout.font();
1406 QFontPrivate *fd = QFontPrivate::get(font);
1407 qreal dpiScale = qreal(fd->dpi) / qreal(qt_defaultDpiY());
1408 if (!qFuzzyCompare(p1: dpiScale, p2: 1.0))
1409 QSKIP("Test logic does not work when tabs are scaled by dpi");
1410
1411 layout.setCacheEnabled(true);
1412 // test if this works on the second line.
1413 layout.beginLayout();
1414 QTextLine line = layout.createLine();
1415 line.setLineWidth(220.); // moves 'sit' to next line.
1416 line = layout.createLine();
1417 line.setLineWidth(220.);
1418 layout.endLayout();
1419
1420
1421 QCOMPARE(line.cursorToX(22), 80.);
1422}
1423
1424void tst_QTextLayout::testMultiTab()
1425{
1426 QTextLayout layout("Foo\t\t\tBar.", testFont);
1427
1428 QFont font = layout.font();
1429 QFontPrivate *fd = QFontPrivate::get(font);
1430 qreal dpiScale = qreal(fd->dpi) / qreal(qt_defaultDpiY());
1431 if (!qFuzzyCompare(p1: dpiScale, p2: 1.0))
1432 QSKIP("Test logic does not work when tabs are scaled by dpi");
1433
1434 layout.setCacheEnabled(true);
1435 layout.beginLayout();
1436 QTextLine line = layout.createLine();
1437 line.setLineWidth(1000.);
1438 layout.endLayout();
1439
1440 QCOMPARE(line.cursorToX(6), 80. * 3);
1441}
1442
1443void tst_QTextLayout::testTabsInAlignedParag()
1444{
1445 QTextLayout layout("Foo\tsome more words", testFont);
1446 layout.setCacheEnabled(true);
1447
1448 QFont font = layout.font();
1449 QFontPrivate *fd = QFontPrivate::get(font);
1450 qreal dpiScale = qreal(fd->dpi) / qreal(qt_defaultDpiY());
1451 if (!qFuzzyCompare(p1: dpiScale, p2: 1.0))
1452 QSKIP("Test logic does not work when tabs are scaled by dpi");
1453
1454 QTextOption option = layout.textOption();
1455 // right
1456 option.setAlignment(Qt::AlignRight);
1457 layout.setTextOption(option);
1458
1459 layout.beginLayout();
1460 QTextLine line = layout.createLine();
1461 line.setLineWidth(300.);
1462 layout.endLayout();
1463
1464 const qreal textWidth = 80 + 15 * TESTFONT_SIZE; // 15 chars right of the tab
1465 QCOMPARE(line.naturalTextWidth(), textWidth);
1466 QCOMPARE(line.cursorToX(0), 300. - textWidth);
1467
1468 // centered
1469 option.setAlignment(Qt::AlignCenter);
1470 layout.setTextOption(option);
1471
1472 layout.beginLayout();
1473 line = layout.createLine();
1474 line.setLineWidth(300.);
1475 layout.endLayout();
1476
1477 QCOMPARE(line.naturalTextWidth(), textWidth);
1478 QCOMPARE(line.cursorToX(0), (300. - textWidth) / 2.);
1479
1480 // justified
1481 option.setAlignment(Qt::AlignJustify);
1482 layout.setTextOption(option);
1483
1484 layout.beginLayout();
1485 line = layout.createLine();
1486 line.setLineWidth(textWidth - 10); // make the last word slip to the next line so justification actually happens.
1487 layout.endLayout();
1488
1489 QCOMPARE(line.cursorToX(0), 0.);
1490 QCOMPARE(line.cursorToX(4), 80.);
1491
1492 //QTextLayout layout2("Foo\tUt wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis", testFont); // means it will be more then one line long.
1493}
1494
1495void tst_QTextLayout::testRightTab()
1496{
1497 QTextLayout layout("Foo\tLorem ipsum te sit\tBar baz\tText\tEnd", testFont);
1498 /* ^ a ^ b ^ c ^ d
1499 a = 4, b = 22, c = 30, d = 35 (position)
1500
1501 I expect the output to be:
1502 Foo Lorem ipsum te
1503 sit Bar Baz
1504 Text End
1505
1506 a) tab replaced with a single space due to the text not fitting before the tab.
1507 b) tab takes space so the text until the 3th tab fits to the tab pos.
1508 c) tab is after last tab (both auto and defined) and thus moves text to start of next line.
1509 d) tab takes space so text until enter fits to tab pos.
1510 */
1511 layout.setCacheEnabled(true);
1512
1513 QFont font = layout.font();
1514 QFontPrivate *fd = QFontPrivate::get(font);
1515 qreal dpiScale = qreal(fd->dpi) / qreal(qt_defaultDpiY());
1516 if (!qFuzzyCompare(p1: dpiScale, p2: 1.0))
1517 QSKIP("Test logic does not work when tabs are scaled by dpi");
1518
1519 QTextOption option = layout.textOption();
1520 QList<QTextOption::Tab> tabs;
1521 QTextOption::Tab tab;
1522 tab.type = QTextOption::RightTab;
1523 tab.position = 190; // which means only 15(.8) chars of our test font fit left of it.
1524 tabs.append(t: tab);
1525 option.setTabs(tabs);
1526 layout.setTextOption(option);
1527
1528 layout.beginLayout();
1529 QTextLine line1 = layout.createLine();
1530 line1.setLineWidth(220.);
1531 // qDebug() << "=====line 2";
1532 QTextLine line2 = layout.createLine();
1533 QVERIFY(line2.isValid());
1534 line2.setLineWidth(220.);
1535 // qDebug() << "=====line 3";
1536 QTextLine line3 = layout.createLine();
1537 QVERIFY(line3.isValid());
1538 line3.setLineWidth(220.);
1539 // qDebug() << "=====line 4";
1540 QTextLine line4 = layout.createLine();
1541 QVERIFY(! line4.isValid());
1542 layout.endLayout();
1543 // qDebug() << "--------";
1544
1545 QCOMPARE(line1.cursorToX(4), 3. * TESTFONT_SIZE ); // a
1546 QCOMPARE(line1.textLength(), 19);
1547 QCOMPARE(line2.cursorToX(23), 190. - 7. * TESTFONT_SIZE); // b
1548 QCOMPARE(line2.textLength(), 12);
1549 QCOMPARE(line3.cursorToX(31), 0.); // c
1550 QCOMPARE(line3.cursorToX(36), 190 - 3. * TESTFONT_SIZE); // d
1551}
1552
1553void tst_QTextLayout::testCenteredTab()
1554{
1555 QTextLayout layout("Foo\tBar", testFont);
1556
1557 QFont font = layout.font();
1558 QFontPrivate *fd = QFontPrivate::get(font);
1559 qreal dpiScale = qreal(fd->dpi) / qreal(qt_defaultDpiY());
1560 if (!qFuzzyCompare(p1: dpiScale, p2: 1.0))
1561 QSKIP("Test logic does not work when tabs are scaled by dpi");
1562
1563 layout.setCacheEnabled(true);
1564 // test if centering the tab works. We expect the center of 'Bar.' to be at the tab point.
1565 QTextOption option = layout.textOption();
1566 QList<QTextOption::Tab> tabs;
1567 QTextOption::Tab tab(150, QTextOption::CenterTab);
1568 tabs.append(t: tab);
1569 option.setTabs(tabs);
1570 layout.setTextOption(option);
1571
1572 layout.beginLayout();
1573 QTextLine line = layout.createLine();
1574 line.setLineWidth(200.);
1575 layout.endLayout();
1576
1577 const qreal wordLength = 3 * TESTFONT_SIZE; // the length of 'Bar'
1578 QCOMPARE(line.cursorToX(4), 150 - wordLength / 2.);
1579}
1580
1581void tst_QTextLayout::testDelimiterTab()
1582{
1583 QTextLayout layout("Foo\tBar. Barrabas", testFont);
1584
1585 QFont font = layout.font();
1586 QFontPrivate *fd = QFontPrivate::get(font);
1587 qreal dpiScale = qreal(fd->dpi) / qreal(qt_defaultDpiY());
1588 if (!qFuzzyCompare(p1: dpiScale, p2: 1.0))
1589 QSKIP("Test logic does not work when tabs are scaled by dpi");
1590
1591 layout.setCacheEnabled(true);
1592 // try the different delimiter characters to see if the alignment works there.
1593 QTextOption option = layout.textOption();
1594 QList<QTextOption::Tab> tabs;
1595 QTextOption::Tab tab(100, QTextOption::DelimiterTab, QChar('.'));
1596 tabs.append(t: tab);
1597 option.setTabs(tabs);
1598 layout.setTextOption(option);
1599
1600 layout.beginLayout();
1601 QTextLine line = layout.createLine();
1602 line.setLineWidth(200.);
1603 layout.endLayout();
1604
1605 const qreal distanceBeforeTab = 3.5 * TESTFONT_SIZE; // the length of 'bar' and half the width of the dot.
1606 QCOMPARE(line.cursorToX(4), 100 - distanceBeforeTab);
1607}
1608
1609void tst_QTextLayout::testLineBreakingAllSpaces()
1610{
1611 QTextLayout layout(" 123", testFont); // thats 20 spaces
1612 layout.setCacheEnabled(true);
1613 const qreal firstLineWidth = 17 * TESTFONT_SIZE;
1614 layout.beginLayout();
1615 QTextLine line1 = layout.createLine();
1616 line1.setLineWidth(firstLineWidth);
1617 QTextLine line2 = layout.createLine();
1618 line2.setLineWidth(1000); // the rest
1619 layout.endLayout();
1620 QCOMPARE(line1.width(), firstLineWidth);
1621 QCOMPARE(line1.naturalTextWidth(), 0.); // spaces don't take space
1622 QCOMPARE(line1.textLength(), 20);
1623 QCOMPARE(line2.textLength(), 3);
1624 QCOMPARE(line2.naturalTextWidth(), 3. * TESTFONT_SIZE);
1625}
1626
1627void tst_QTextLayout::tabsForRtl()
1628{
1629 QString word(QChar(0x5e9)); // a hebrew character
1630 word = word + word + word; // 3 hebrew characters ;)
1631
1632 QTextLayout layout(word +'\t'+ word +'\t'+ word +'\t'+ word, testFont);
1633//QTextLayout layout(word +' '+ word +' '+ word +' '+ word, testFont);// tester ;)
1634 /* ^ a ^ b ^ c
1635 a = 4, b = 8, c = 12, d = 16 (position)
1636
1637 a) Left tab in RTL is a righ tab; so a is at width - 80
1638 b) Like a
1639 c) right tab on RTL is a left tab; so its at width - 240
1640 d) center tab is still a centered tab.
1641 */
1642 layout.setCacheEnabled(true);
1643
1644 QFont font = layout.font();
1645 QFontPrivate *fd = QFontPrivate::get(font);
1646 qreal dpiScale = qreal(fd->dpi) / qreal(qt_defaultDpiY());
1647 if (!qFuzzyCompare(p1: dpiScale, p2: 1.0))
1648 QSKIP("Test logic does not work when tabs are scaled by dpi");
1649
1650 QTextOption option = layout.textOption();
1651 QList<QTextOption::Tab> tabs;
1652 QTextOption::Tab tab;
1653 tab.position = 80;
1654 tabs.append(t: tab);
1655 tab.position = 160;
1656 tabs.append(t: tab);
1657 tab.position = 240;
1658 tab.type = QTextOption::RightTab;
1659 tabs.append(t: tab);
1660 option.setTabs(tabs);
1661 option.setTextDirection(Qt::RightToLeft);
1662 option.setAlignment(Qt::AlignRight);
1663 layout.setTextOption(option);
1664
1665 layout.beginLayout();
1666 QTextLine line = layout.createLine();
1667 const qreal WIDTH = 400.;
1668 line.setLineWidth(WIDTH);
1669 layout.endLayout();
1670
1671//qDebug() << "layout ended --------------";
1672
1673 QCOMPARE(line.cursorToX(0), WIDTH);
1674 QCOMPARE(line.cursorToX(1), WIDTH - TESTFONT_SIZE); // check its right-aligned
1675 QCOMPARE(line.cursorToX(4), WIDTH - 80 + 3 * TESTFONT_SIZE);
1676 QCOMPARE(line.cursorToX(8), WIDTH - 160 + 3 * TESTFONT_SIZE);
1677 QCOMPARE(line.cursorToX(12), WIDTH - 240);
1678}
1679
1680QT_BEGIN_NAMESPACE
1681Q_GUI_EXPORT int qt_defaultDpiY();
1682QT_END_NAMESPACE
1683
1684void tst_QTextLayout::testTabDPIScale()
1685{
1686 class MyPaintDevice : public QPaintDevice {
1687 QPaintEngine *paintEngine () const { return 0; }
1688 int metric (QPaintDevice::PaintDeviceMetric metric) const {
1689 switch(metric) {
1690 case QPaintDevice::PdmWidth:
1691 case QPaintDevice::PdmHeight:
1692 case QPaintDevice::PdmWidthMM:
1693 case QPaintDevice::PdmHeightMM:
1694 case QPaintDevice::PdmNumColors:
1695 return INT_MAX;
1696 case QPaintDevice::PdmDepth:
1697 return 32;
1698 case QPaintDevice::PdmDpiX:
1699 case QPaintDevice::PdmDpiY:
1700 case QPaintDevice::PdmPhysicalDpiX:
1701 case QPaintDevice::PdmPhysicalDpiY:
1702 return 72;
1703 case QPaintDevice::PdmDevicePixelRatio:
1704 case QPaintDevice::PdmDevicePixelRatioScaled:
1705 ; // fall through
1706 }
1707 return 0;
1708 }
1709 };
1710
1711 MyPaintDevice pd;
1712
1713 QTextLayout layout("text1\ttext2\ttext3\tend", testFont, &pd);
1714 layout.setCacheEnabled(true);
1715
1716 QTextOption option = layout.textOption();
1717 QList<QTextOption::Tab> tabs;
1718 QTextOption::Tab tab;
1719 tab.position = 300;
1720 tabs.append(t: tab);
1721
1722 tab.position = 600;
1723 tab.type = QTextOption::RightTab;
1724 tabs.append(t: tab);
1725
1726 tab.position = 800;
1727 tab.type = QTextOption::CenterTab;
1728 tabs.append(t: tab);
1729 option.setTabs(tabs);
1730 layout.setTextOption(option);
1731
1732 layout.beginLayout();
1733 QTextLine line = layout.createLine();
1734 line.setLineWidth(1500.);
1735 layout.endLayout();
1736 QCOMPARE(line.cursorToX(0), 0.);
1737 QCOMPARE(line.cursorToX(1), (double) TESTFONT_SIZE); // check that the font does not resize
1738 qreal scale = 72 / (qreal) qt_defaultDpiY();
1739 // lets do the transformation of deminishing resolution that QFixed has as effect.
1740 int fixedScale = (int)( scale * qreal(64)); // into a QFixed
1741 scale = ((qreal)fixedScale)/(qreal)64; // and out of a QFixed
1742
1743 QCOMPARE(line.cursorToX(6), tabs.at(0).position * scale);
1744 QCOMPARE(line.cursorToX(12), tabs.at(1).position * scale - TESTFONT_SIZE * 5);
1745 QCOMPARE(line.cursorToX(18), tabs.at(2).position * scale - TESTFONT_SIZE * 3 / 2.0);
1746}
1747#endif
1748
1749void tst_QTextLayout::tabHeight()
1750{
1751 QTextLayout layout("\t");
1752 layout.setCacheEnabled(true);
1753 layout.beginLayout();
1754 QTextLine line = layout.createLine();
1755 layout.endLayout();
1756
1757 QCOMPARE(qRound(line.ascent()), QFontMetrics(layout.font()).ascent());
1758 QCOMPARE(qRound(line.descent()), QFontMetrics(layout.font()).descent());
1759}
1760
1761void tst_QTextLayout::capitalization_allUpperCase()
1762{
1763 QFont font;
1764 font.setCapitalization(QFont::AllUppercase);
1765 QTextLayout layout("Test", font);
1766 layout.setCacheEnabled(true);
1767 layout.beginLayout();
1768 layout.createLine();
1769 layout.endLayout();
1770
1771 QTextEngine *engine = layout.engine();
1772 engine->itemize();
1773 QCOMPARE(engine->layoutData->items.count(), 1);
1774 QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::Uppercase));
1775}
1776
1777void tst_QTextLayout::capitalization_allUpperCase_newline()
1778{
1779 QFont font;
1780 font.setCapitalization(QFont::AllUppercase);
1781
1782 QString tmp = "hello\nworld!";
1783 tmp.replace(before: QLatin1Char('\n'), after: QChar::LineSeparator);
1784
1785 QTextLayout layout(tmp, font);
1786 layout.setCacheEnabled(true);
1787 layout.beginLayout();
1788 layout.createLine();
1789 layout.endLayout();
1790
1791 QTextEngine *engine = layout.engine();
1792 engine->itemize();
1793 QCOMPARE(engine->layoutData->items.count(), 3);
1794 QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::Uppercase));
1795 QCOMPARE(engine->layoutData->items.at(1).analysis.flags, ushort(QScriptAnalysis::LineOrParagraphSeparator));
1796 QCOMPARE(engine->layoutData->items.at(2).analysis.flags, ushort(QScriptAnalysis::Uppercase));
1797}
1798
1799void tst_QTextLayout::capitalization_allLowerCase()
1800{
1801 QFont font;
1802 font.setCapitalization(QFont::AllLowercase);
1803 QTextLayout layout("Test", font);
1804 layout.setCacheEnabled(true);
1805 layout.beginLayout();
1806 layout.createLine();
1807 layout.endLayout();
1808
1809 QTextEngine *engine = layout.engine();
1810 engine->itemize();
1811 QCOMPARE(engine->layoutData->items.count(), 1);
1812 QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::Lowercase));
1813}
1814
1815void tst_QTextLayout::capitalization_smallCaps()
1816{
1817 QFont font;
1818 font.setCapitalization(QFont::SmallCaps);
1819 QTextLayout layout("Test", font);
1820 layout.setCacheEnabled(true);
1821 layout.beginLayout();
1822 layout.createLine();
1823 layout.endLayout();
1824
1825 QTextEngine *engine = layout.engine();
1826 engine->itemize();
1827 QCOMPARE(engine->layoutData->items.count(), 2);
1828 QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::None));
1829 QCOMPARE(engine->layoutData->items.at(1).analysis.flags, ushort(QScriptAnalysis::SmallCaps));
1830}
1831
1832void tst_QTextLayout::capitalization_capitalize()
1833{
1834 QFont font;
1835 font.setCapitalization(QFont::Capitalize);
1836 QTextLayout layout("hello\tworld", font);
1837 layout.setCacheEnabled(true);
1838 layout.beginLayout();
1839 layout.createLine();
1840 layout.endLayout();
1841
1842 QTextEngine *engine = layout.engine();
1843 engine->itemize();
1844 QCOMPARE(engine->layoutData->items.count(), 5);
1845 QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::Uppercase));
1846 QCOMPARE(engine->layoutData->items.at(1).analysis.flags, ushort(QScriptAnalysis::None));
1847 QCOMPARE(engine->layoutData->items.at(2).analysis.flags, ushort(QScriptAnalysis::Tab));
1848 QCOMPARE(engine->layoutData->items.at(3).analysis.flags, ushort(QScriptAnalysis::Uppercase));
1849 QCOMPARE(engine->layoutData->items.at(4).analysis.flags, ushort(QScriptAnalysis::None));
1850}
1851
1852void tst_QTextLayout::longText()
1853{
1854 QString longText(128000, 'a');
1855
1856 {
1857 QTextLayout layout(longText);
1858 layout.setCacheEnabled(true);
1859 layout.beginLayout();
1860 QTextLine line = layout.createLine();
1861 layout.endLayout();
1862 QVERIFY(line.isValid());
1863 QVERIFY(line.cursorToX(line.textLength() - 1) > 0);
1864 }
1865
1866 for (int cap = QFont::MixedCase; cap < QFont::Capitalize + 1; ++cap) {
1867 QFont f;
1868 f.setCapitalization(QFont::Capitalization(cap));
1869 QTextLayout layout(longText, f);
1870 layout.setCacheEnabled(true);
1871 layout.beginLayout();
1872 QTextLine line = layout.createLine();
1873 layout.endLayout();
1874 QVERIFY(line.isValid());
1875 QVERIFY(line.cursorToX(line.textLength() - 1) > 0);
1876 }
1877
1878 {
1879 QTextLayout layout(longText);
1880 layout.setCacheEnabled(true);
1881 layout.setFlags(Qt::TextForceLeftToRight);
1882 layout.beginLayout();
1883 QTextLine line = layout.createLine();
1884 layout.endLayout();
1885 QVERIFY(line.isValid());
1886 QVERIFY(line.cursorToX(line.textLength() - 1) > 0);
1887 }
1888
1889 {
1890 QTextLayout layout(longText);
1891 layout.setCacheEnabled(true);
1892 layout.setFlags(Qt::TextForceRightToLeft);
1893 layout.beginLayout();
1894 QTextLine line = layout.createLine();
1895 layout.endLayout();
1896 QVERIFY(line.isValid());
1897 QVERIFY(line.cursorToX(line.textLength() - 1) > 0);
1898 }
1899
1900 {
1901 QTextLayout layout(QString("Qt rocks! ").repeated(times: 200000));
1902 layout.setCacheEnabled(true);
1903 layout.beginLayout();
1904 forever {
1905 QTextLine line = layout.createLine();
1906 if (!line.isValid())
1907 break;
1908 }
1909 layout.endLayout();
1910 QFontMetricsF fm(layout.font());
1911 QVERIFY(layout.maximumWidth() - fm.horizontalAdvance(' ') <= QFIXED_MAX);
1912 }
1913
1914 {
1915 QTextLayout layout(QString("AAAAAAAA").repeated(times: 200000));
1916 layout.setCacheEnabled(true);
1917 layout.beginLayout();
1918 forever {
1919 QTextLine line = layout.createLine();
1920 if (!line.isValid())
1921 break;
1922 }
1923 layout.endLayout();
1924 QFontMetricsF fm(layout.font());
1925 QVERIFY(layout.maximumWidth() - fm.horizontalAdvance('A') <= QFIXED_MAX);
1926 }
1927}
1928
1929void tst_QTextLayout::widthOfTabs()
1930{
1931 QTextEngine engine;
1932 engine.text = "ddd\t\t";
1933 engine.ignoreBidi = true;
1934 engine.itemize();
1935 QCOMPARE(qRound(engine.width(0, 5)), qRound(engine.boundingBox(0, 5).width));
1936}
1937
1938void tst_QTextLayout::columnWrapWithTabs()
1939{
1940 QTextLayout textLayout;
1941 {
1942 QTextOption textOption;
1943 textOption.setWrapMode(QTextOption::WordWrap);
1944 textLayout.setTextOption(textOption);
1945 }
1946
1947 // Make sure string with spaces does not break
1948 {
1949 QString text = "Foo bar foo bar foo bar";
1950 textLayout.setText(text);
1951
1952 textLayout.beginLayout();
1953 QTextLine line = textLayout.createLine();
1954 line.setNumColumns(30);
1955 QCOMPARE(line.textLength(), text.length());
1956 textLayout.endLayout();
1957 }
1958
1959 // Make sure string with tabs breaks
1960 {
1961 QString text = "Foo\tbar\tfoo\tbar\tfoo\tbar";
1962 textLayout.setText(text);
1963 textLayout.beginLayout();
1964 QTextLine line = textLayout.createLine();
1965 line.setNumColumns(30);
1966 QVERIFY(line.textLength() < text.length());
1967 textLayout.endLayout();
1968 }
1969
1970}
1971
1972void tst_QTextLayout::boundingRectForUnsetLineWidth()
1973{
1974 QTextLayout layout("FOOBAR");
1975 layout.setCacheEnabled(true);
1976 layout.beginLayout();
1977 QTextLine line = layout.createLine();
1978 layout.endLayout();
1979
1980 QCOMPARE(layout.boundingRect().width(), line.naturalTextWidth());
1981}
1982
1983void tst_QTextLayout::boundingRectForSetLineWidth()
1984{
1985 QTextLayout layout("FOOBAR");
1986 layout.setCacheEnabled(true);
1987 layout.beginLayout();
1988 QTextLine line = layout.createLine();
1989 line.setLineWidth(QFIXED_MAX - 1);
1990 layout.endLayout();
1991
1992 QCOMPARE(layout.boundingRect().width(), qreal(QFIXED_MAX - 1));
1993}
1994
1995void tst_QTextLayout::lineWidthFromBOM()
1996{
1997 const QString string(QChar(0xfeff)); // BYTE ORDER MARK
1998 QTextLayout layout(string);
1999 layout.beginLayout();
2000 QTextLine line = layout.createLine();
2001 line.setLineWidth(INT_MAX / 256);
2002 layout.endLayout();
2003
2004 // Don't spin into an infinite loop
2005 }
2006
2007void tst_QTextLayout::glyphLessItems()
2008{
2009 {
2010 QTextLayout layout;
2011 layout.setText("\t\t");
2012 layout.beginLayout();
2013 layout.createLine();
2014 layout.endLayout();
2015 }
2016
2017 {
2018 QTextLayout layout;
2019 layout.setText(QString::fromLatin1(str: "AA") + QChar(QChar::LineSeparator));
2020 layout.beginLayout();
2021 layout.createLine();
2022 layout.endLayout();
2023 }
2024}
2025
2026void tst_QTextLayout::textWidthVsWIdth()
2027{
2028 QTextLayout layout;
2029 layout.setCacheEnabled(true);
2030 QTextOption opt;
2031 opt.setWrapMode(QTextOption::WrapAnywhere);
2032#if defined(Q_OS_WIN)
2033 layout.setFont(QFont(QString::fromLatin1("Arial")));
2034#endif
2035 layout.setTextOption(opt);
2036 layout.setText(QString::fromLatin1(
2037 str: "g++ -c -m64 -pipe -g -fvisibility=hidden -fvisibility-inlines-hidden -Wall -W -D_REENTRANT -fPIC -DCORE_LIBRARY -DIDE_LIBRARY_BASENAME=\"lib\" -DWITH_TESTS "
2038 "-DQT_NO_CAST_TO_ASCII -DQT_USE_FAST_OPERATOR_PLUS -DQT_USE_FAST_CONCATENATION -DQT_PLUGIN -DQT_TESTLIB_LIB -DQT_SCRIPT_LIB -DQT_SVG_LIB -DQT_SQL_LIB -DQT_XM"
2039 "L_LIB -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_SHARED -I../../../../qt-qml/mkspecs/linux-g++-64 -I. -I../../../../qt-qml/include/QtCore -I../../../."
2040 "./qt-qml/include/QtNetwork -I../../../../qt-qml/include/QtGui -I../../../../qt-qml/include/QtXml -I../../../../qt-qml/include/QtSql -I../../../../qt-qml/inc"
2041 "lude/QtSvg -I../../../../qt-qml/include/QtScript -I../../../../qt-qml/include/QtTest -I../../../../qt-qml/include -I../../../../qt-qml/include/QtHelp -I../."
2042 "./libs -I/home/ettrich/dev/creator/tools -I../../plugins -I../../shared/scriptwrapper -I../../libs/3rdparty/botan/build -Idialogs -Iactionmanager -Ieditorma"
2043 "nager -Iprogressmanager -Iscriptmanager -I.moc/debug-shared -I.uic -o .obj/debug-shared/sidebar.o sidebar.cpp"));
2044
2045 // The naturalTextWidth includes right bearing, but should never be LARGER than line width if
2046 // there is space for at least one character. Unfortunately that assumption may not hold if the
2047 // font engine fails to report an accurate minimum right bearing for the font, eg. when the
2048 // minimum right bearing reported by the font engine doesn't cover all the glyphs in the font.
2049 // The result is that this test may fail in some cases. We should fix this by running the test
2050 // with a font that we know have no suprising right bearings. See qtextlayout.cpp for details.
2051 QFontMetricsF fontMetrics(layout.font());
2052 QSet<char16_t> checked;
2053 qreal minimumRightBearing = 0.0;
2054 for (int i = 0; i < layout.text().size(); ++i) {
2055 QChar c = layout.text().at(i);
2056 if (!checked.contains(value: c.unicode())) {
2057 qreal rightBearing = fontMetrics.rightBearing(c);
2058 if (rightBearing < minimumRightBearing)
2059 minimumRightBearing = rightBearing;
2060 checked.insert(value: c.unicode());
2061 }
2062 }
2063 if (minimumRightBearing < fontMetrics.minRightBearing())
2064 QSKIP("Font reports invalid minimum right bearing, and can't be used for this test.");
2065
2066 for (int width = 100; width < 1000; ++width) {
2067 layout.beginLayout();
2068 QTextLine line = layout.createLine();
2069 line.setLineWidth(width);
2070 layout.endLayout();
2071
2072 qreal textWidthIsLargerBy = qMax(a: qreal(0), b: line.naturalTextWidth() - line.width());
2073 qreal thisMustBeZero = 0;
2074 QCOMPARE(textWidthIsLargerBy, thisMustBeZero);
2075 }
2076}
2077
2078void tst_QTextLayout::textWithSurrogates_qtbug15679()
2079{
2080 QString str = QString::fromUtf8(str: "🀀a🀀");
2081 QTextLayout layout(str);
2082 layout.setCacheEnabled(true);
2083 layout.beginLayout();
2084 QTextLine line = layout.createLine();
2085 layout.endLayout();
2086
2087 qreal x[6];
2088 for (int i = 0; i < 6; i++)
2089 x[i] = line.cursorToX(cursorPos: i);
2090
2091 // If the first and third character are using the same
2092 // font, they must have the same advance (since they
2093 // are surrogate pairs, we need to add two for each
2094 // character)
2095 QCOMPARE(x[2] - x[0], x[5] - x[3]);
2096}
2097
2098void tst_QTextLayout::textWidthWithStackedTextEngine()
2099{
2100 QString text = QString::fromUtf8(str: "คลิก ถัดไป เพื่อดำเนินการต่อ");
2101
2102 QTextLayout layout(text);
2103 layout.setCacheEnabled(true);
2104 layout.beginLayout();
2105 QTextLine line = layout.createLine();
2106 layout.endLayout();
2107
2108 QStackTextEngine layout2(text, layout.font());
2109
2110 QVERIFY(layout2.width(0, text.size()).toReal() >= line.naturalTextWidth());
2111}
2112
2113void tst_QTextLayout::textWidthWithLineSeparator()
2114{
2115 QString s1("Save Project"), s2("Save Project\ntest");
2116 s2.replace(before: '\n', after: QChar::LineSeparator);
2117
2118 QTextLayout layout1(s1), layout2(s2);
2119 layout1.beginLayout();
2120 layout2.beginLayout();
2121
2122 QTextLine line1 = layout1.createLine();
2123 QTextLine line2 = layout2.createLine();
2124 line1.setLineWidth(0x1000);
2125 line2.setLineWidth(0x1000);
2126 QCOMPARE(line1.naturalTextWidth(), line2.naturalTextWidth());
2127}
2128
2129void tst_QTextLayout::cursorInLigatureWithMultipleLines()
2130{
2131 QTextLayout layout("first line finish", QFont("Times", 20));
2132 layout.setCacheEnabled(true);
2133 layout.beginLayout();
2134 QTextLine line = layout.createLine();
2135 line.setNumColumns(10);
2136 QTextLine line2 = layout.createLine();
2137 layout.endLayout();
2138
2139 // The second line will be "finish"
2140 QCOMPARE(layout.text().mid(line2.textStart(), line2.textLength()), QString::fromLatin1("finish"));
2141
2142 QVERIFY(line.cursorToX(1) != line.cursorToX(0));
2143 QCOMPARE(line2.cursorToX(line2.textStart()), line.cursorToX(0));
2144 QCOMPARE(line2.cursorToX(line2.textStart() + 1), line.cursorToX(1));
2145}
2146
2147void tst_QTextLayout::xToCursorForLigatures()
2148{
2149#if defined(Q_OS_WIN32)
2150 QTextLayout layout("fi", QFont("Calibri", 20));
2151#else
2152 QTextLayout layout("fi", QFont("Times", 20));
2153#endif
2154
2155 layout.setCacheEnabled(true);
2156 layout.beginLayout();
2157 QTextLine line = layout.createLine();
2158 layout.endLayout();
2159
2160 QVERIFY(line.xToCursor(0) != line.xToCursor(line.naturalTextWidth() / 2));
2161
2162 // U+0061 U+0308
2163 QTextLayout layout2(QString::fromUtf8(str: "\x61\xCC\x88"), QFont("Times", 20));
2164 layout2.setCacheEnabled(true);
2165 layout2.beginLayout();
2166 line = layout2.createLine();
2167 layout2.endLayout();
2168
2169 qreal width = line.naturalTextWidth();
2170 QVERIFY(line.xToCursor(0) == line.xToCursor(width / 2) ||
2171 line.xToCursor(width) == line.xToCursor(width / 2));
2172}
2173
2174void tst_QTextLayout::cursorInNonStopChars()
2175{
2176 QTextLayout layout(QString::fromUtf8(str: "\xE0\xA4\xA4\xE0\xA5\x8D\xE0\xA4\xA8"));
2177 layout.setCacheEnabled(true);
2178 layout.beginLayout();
2179 QTextLine line = layout.createLine();
2180 layout.endLayout();
2181
2182 QCOMPARE(line.cursorToX(1), line.cursorToX(3));
2183 QCOMPARE(line.cursorToX(2), line.cursorToX(3));
2184}
2185
2186void tst_QTextLayout::justifyTrailingSpaces()
2187{
2188 QTextLayout layout(QStringLiteral(" t"));
2189 layout.setTextOption(QTextOption(Qt::AlignJustify));
2190 layout.beginLayout();
2191
2192 QTextLine line = layout.createLine();
2193 line.setLineWidth(5);
2194
2195 layout.endLayout();
2196
2197 QVERIFY(qFuzzyIsNull(layout.lineAt(0).cursorToX(0)));
2198}
2199
2200void tst_QTextLayout::nbsp()
2201{
2202 QString s = QString() + QChar(' ') + QChar('a') + QString(10, QChar::Nbsp) + QChar('a') + QChar(' ') + QChar('A');
2203 QString text = s + s + s + s + s + s + s + s + s + s + s + s + s + s;
2204 QTextLayout layout(text);
2205 layout.setCacheEnabled(true);
2206 layout.beginLayout();
2207 layout.createLine();
2208 layout.endLayout();
2209
2210 int naturalWidth = qCeil(v: layout.lineAt(i: 0).naturalTextWidth());
2211 int lineWidth = naturalWidth;
2212
2213 layout.beginLayout();
2214 QTextLine line = layout.createLine();
2215 while (lineWidth-- > naturalWidth / 2) {
2216 line.setLineWidth(lineWidth);
2217 QVERIFY(text.at(line.textLength()-1).unicode() != QChar::Nbsp);
2218 }
2219
2220 layout.endLayout();
2221}
2222
2223void tst_QTextLayout::layoutWithCustomTabStops()
2224{
2225 QScopedPointer<QTextLayout> textLayout(new QTextLayout);
2226 QList<QTextOption::Tab> tabStops;
2227
2228 const int tabWidth = 18;
2229 const int maxTabPos = 2500;
2230 for (int tabPos = tabWidth; tabPos < maxTabPos; tabPos += tabWidth)
2231 tabStops << QTextOption::Tab(tabPos, QTextOption::LeftTab);
2232
2233 QTextOption textOption;
2234 textOption.setTabs(tabStops);
2235 textLayout->setTextOption(textOption);
2236
2237 textLayout->setText(QStringLiteral("\ta aa aa aa aa aa aa"));
2238
2239 textLayout->beginLayout();
2240 textLayout->createLine();
2241 textLayout->endLayout();
2242
2243 qreal shortWidth = textLayout->maximumWidth();
2244
2245 textLayout->setText(QStringLiteral("\ta aa aa aa aa aa aa aa aa aa aa aa aa a"));
2246
2247 textLayout->beginLayout();
2248 textLayout->createLine();
2249 textLayout->endLayout();
2250
2251 qreal longWidth = textLayout->maximumWidth();
2252
2253 QVERIFY(longWidth > shortWidth);
2254}
2255
2256void tst_QTextLayout::noModificationOfInputString()
2257{
2258 QString s = QString(QChar(QChar::LineSeparator));
2259 {
2260 QTextLayout layout;
2261 layout.setText(s);
2262
2263 layout.beginLayout();
2264 layout.createLine();
2265 layout.endLayout();
2266
2267 QCOMPARE(s.size(), 1);
2268 QCOMPARE(s.at(0), QChar(QChar::LineSeparator));
2269 }
2270
2271 {
2272 QTextLayout layout;
2273 layout.setText(s);
2274
2275 QTextOption option;
2276 option.setFlags(QTextOption::ShowLineAndParagraphSeparators);
2277 layout.setTextOption(option);
2278
2279 layout.beginLayout();
2280 layout.createLine();
2281 layout.endLayout();
2282
2283 QCOMPARE(s.size(), 1);
2284 QCOMPARE(s.at(0), QChar(QChar::LineSeparator));
2285 }
2286}
2287
2288void tst_QTextLayout::showLineAndParagraphSeparatorsCrash()
2289{
2290 QString s = QString(100000, QChar('a')) + QChar(QChar::LineSeparator);
2291 {
2292 QTextLayout layout;
2293 layout.setText(s);
2294
2295 QTextOption option;
2296 option.setFlags(QTextOption::ShowLineAndParagraphSeparators);
2297 layout.setTextOption(option);
2298
2299 layout.beginLayout();
2300 layout.createLine();
2301 layout.endLayout();
2302 }
2303}
2304
2305void tst_QTextLayout::superscriptCrash_qtbug53911()
2306{
2307 static int fontSizes = 64;
2308 static QString layoutText = "THIS IS SOME EXAMPLE TEXT THIS IS SOME EXAMPLE TEXT";
2309
2310 QList<QTextLayout*> textLayouts;
2311 for (int i = 0; i < fontSizes; ++i) {
2312 for (int j = 0; j < 4; ++j) {
2313 QTextLayout* newTextLayout = new QTextLayout();
2314 newTextLayout->setText(layoutText);
2315 QTextLayout::FormatRange formatRange;
2316
2317 formatRange.format.setFont(QFont());
2318 formatRange.format.setFontPointSize(i + 5);
2319
2320 switch (j) {
2321 case 0:
2322 formatRange.format.setFontWeight(QFont::Normal);
2323 formatRange.format.setFontItalic(false);
2324 break;
2325 case 1:
2326 formatRange.format.setFontWeight(QFont::Bold);
2327 formatRange.format.setFontItalic(false);
2328 break;
2329 case 2:
2330 formatRange.format.setFontWeight(QFont::Bold);
2331 formatRange.format.setFontItalic(true);
2332 break;
2333 case 3:
2334 formatRange.format.setFontWeight(QFont::Normal);
2335 formatRange.format.setFontItalic(true);
2336 break;
2337 }
2338
2339 formatRange.format.setVerticalAlignment( QTextCharFormat::AlignSuperScript);
2340
2341 formatRange.start = 0;
2342 formatRange.length = layoutText.size();
2343 newTextLayout->setFormats({formatRange});
2344
2345 textLayouts.push_front(t: newTextLayout);
2346 }
2347 }
2348
2349 // This loop would crash before fix for QTBUG-53911
2350 foreach (QTextLayout *textLayout, textLayouts) {
2351 textLayout->beginLayout();
2352 while (textLayout->createLine().isValid());
2353 textLayout->endLayout();
2354 }
2355
2356 qDeleteAll(c: textLayouts);
2357}
2358
2359void tst_QTextLayout::nbspWithFormat()
2360{
2361 QString s1 = QLatin1String("ABCDEF ");
2362 QString s2 = QLatin1String("GHI");
2363 QChar nbsp(QChar::Nbsp);
2364 QString s3 = QLatin1String("JKLMNOPQRSTUVWXYZ");
2365
2366 QTextLayout layout;
2367 layout.setText(s1 + s2 + nbsp + s3);
2368
2369 QTextLayout::FormatRange formatRange;
2370 formatRange.start = s1.length() + s2.length();
2371 formatRange.length = 1;
2372 formatRange.format.setFontUnderline(true);
2373
2374 layout.setFormats({formatRange});
2375
2376 layout.beginLayout();
2377 forever {
2378 QTextLine line = layout.createLine();
2379 if (!line.isValid())
2380 break;
2381 line.setLineWidth(1);
2382 }
2383 layout.endLayout();
2384
2385 QCOMPARE(layout.lineCount(), 2);
2386 QCOMPARE(layout.lineAt(0).textStart(), 0);
2387 QCOMPARE(layout.lineAt(0).textLength(), s1.length());
2388 QCOMPARE(layout.lineAt(1).textStart(), s1.length());
2389 QCOMPARE(layout.lineAt(1).textLength(), s2.length() + 1 + s3.length());
2390}
2391
2392void tst_QTextLayout::koreanWordWrap()
2393{
2394 QString s = QString::fromUtf8(str: "안녕하세요 여러분!");
2395 QTextLayout layout;
2396 QTextOption option = layout.textOption();
2397 option.setWrapMode(QTextOption::WordWrap);
2398 option.setFlags(QTextOption::Flag(QTextOption::IncludeTrailingSpaces));
2399 layout.setTextOption(option);
2400 layout.setText(s);
2401
2402 QFontMetrics metrics(layout.font());
2403
2404 layout.beginLayout();
2405 forever {
2406 QTextLine line = layout.createLine();
2407 if (!line.isValid())
2408 break;
2409 line.setLineWidth(metrics.horizontalAdvance(s) * 0.8);
2410 }
2411 layout.endLayout();
2412 QCOMPARE(layout.lineCount(), 2);
2413 QCOMPARE(layout.lineAt(0).textLength(), 6);
2414 QCOMPARE(layout.lineAt(1).textLength(), 4);
2415}
2416
2417void tst_QTextLayout::tooManyDirectionalCharctersCrash_qtbug77819()
2418{
2419 QString data;
2420 data += QString::fromUtf8(str: "\xe2\x81\xa8"); // U+2068 FSI character
2421 data += QString::fromUtf8(str: "\xe2\x81\xa7"); // U+2067 RLI character
2422
2423 // duplicating the text
2424 for (int i = 0; i < 10; i++)
2425 data += data;
2426
2427 // Nothing to test. It must not crash in beginLayout().
2428 QTextLayout tl(data);
2429 tl.beginLayout();
2430 tl.endLayout();
2431}
2432
2433void tst_QTextLayout::softHyphens_data()
2434{
2435 QTest::addColumn<int>(name: "fontSize");
2436
2437 QTest::newRow(dataTag: "12") << 12;
2438 QTest::newRow(dataTag: "14") << 14;
2439 QTest::newRow(dataTag: "16") << 16;
2440}
2441
2442void tst_QTextLayout::softHyphens()
2443{
2444 QFETCH(int, fontSize);
2445 QString text = QStringLiteral("xxxx\u00ad") + QStringLiteral("xxxx\u00ad");
2446
2447 QFont font;
2448 font.setPixelSize(fontSize);
2449 font.setHintingPreference(QFont::PreferNoHinting);
2450 const float xAdvance = QFontMetricsF(font).horizontalAdvance(QChar::fromLatin1(c: 'x'));
2451 float shyWidth = 0.0f;
2452 QTextLayout layout(text, font);
2453 QTextOption option;
2454 option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
2455 layout.setTextOption(option);
2456 {
2457 // Calculate the effective width of a line-ending hyphen
2458 // This calculation is currently done to work-around odditities on
2459 // macOS 11 (see QTBUG-90698).
2460 QTextLayout test(QStringLiteral("x\u00ad"), font);
2461 // Note: This only works because Qt show the soft-hyphen when ending a text.
2462 // This _could_ be considered a bug and the test would need to be changed
2463 // if we stop doing that.
2464 test.beginLayout();
2465 QTextLine line = test.createLine();
2466 line.setLineWidth(10 * xAdvance);
2467 line.setPosition(QPoint(0, 0));
2468 shyWidth = line.naturalTextWidth() - xAdvance;
2469 test.endLayout();
2470 }
2471 qreal linefit;
2472 // Loose fit
2473 // xxxx- |
2474 // xxxx- |
2475 {
2476 int pos = 0;
2477 int y = 0;
2478 layout.beginLayout();
2479 QTextLine line = layout.createLine();
2480 line.setLineWidth(qCeil(v: 5 * xAdvance + shyWidth) + 1);
2481 line.setPosition(QPoint(0, y));
2482 QCOMPARE(line.textStart(), pos);
2483 QCOMPARE(line.textLength(), 5);
2484 linefit = line.naturalTextWidth();
2485 QVERIFY(qAbs(linefit - qCeil(4 * xAdvance + shyWidth)) <= 1.0);
2486
2487 pos += line.textLength();
2488 y += qRound(d: line.ascent() + line.descent());
2489
2490 line = layout.createLine();
2491 line.setLineWidth(qCeil(v: 5 * xAdvance + shyWidth) + 1);
2492 line.setPosition(QPoint(0, y));
2493 QCOMPARE(line.textStart(), pos);
2494 QCOMPARE(line.textLength(), 5);
2495 QVERIFY(qAbs(line.naturalTextWidth() - linefit) <= 1.0);
2496 layout.endLayout();
2497 }
2498
2499 // Tight fit
2500 // xxxx-|
2501 // xxxx-|
2502 {
2503 int pos = 0;
2504 int y = 0;
2505 layout.beginLayout();
2506 QTextLine line = layout.createLine();
2507 line.setLineWidth(qCeil(v: linefit) + 1);
2508 line.setPosition(QPoint(0, y));
2509 QCOMPARE(line.textStart(), pos);
2510 QCOMPARE(line.textLength(), 5);
2511 QVERIFY(qAbs(line.naturalTextWidth() - linefit) <= 1.0);
2512
2513 pos += line.textLength();
2514 y += qRound(d: line.ascent() + line.descent());
2515
2516 line = layout.createLine();
2517 line.setLineWidth(qCeil(v: linefit) + 1);
2518 line.setPosition(QPoint(0, y));
2519 QCOMPARE(line.textStart(), pos);
2520 QCOMPARE(line.textLength(), 5);
2521 QVERIFY(qAbs(line.naturalTextWidth() - linefit) <= 1.0);
2522 layout.endLayout();
2523 }
2524
2525 // Very tight fit
2526 // xxxx|
2527 // xxxx|
2528 // - |
2529 {
2530 int pos = 0;
2531 int y = 0;
2532 layout.beginLayout();
2533 QTextLine line = layout.createLine();
2534 line.setLineWidth(qCeil(v: 4 * xAdvance) + 2);
2535 line.setPosition(QPoint(0, y));
2536 QCOMPARE(line.textStart(), pos);
2537 QCOMPARE(line.textLength(), 4);
2538 QVERIFY(qAbs(line.naturalTextWidth() - qCeil(4 * xAdvance)) <= 1.0);
2539
2540 pos += line.textLength();
2541 y += qRound(d: line.ascent() + line.descent());
2542
2543 line = layout.createLine();
2544 line.setLineWidth(qCeil(v: 4 * xAdvance) + 2);
2545 line.setPosition(QPoint(0, y));
2546 QCOMPARE(line.textStart(), pos);
2547 QCOMPARE(line.textLength(), 5);
2548 QVERIFY(qAbs(line.naturalTextWidth() - qCeil(4 * xAdvance)) <= 1.0);
2549
2550 pos += line.textLength();
2551 y += qRound(d: line.ascent() + line.descent());
2552
2553 line = layout.createLine();
2554 line.setLineWidth(qCeil(v: 4 * xAdvance) + 2);
2555 line.setPosition(QPoint(0, y));
2556 QCOMPARE(line.textStart(), pos);
2557 QCOMPARE(line.textLength(), 1);
2558 QVERIFY(qAbs(line.naturalTextWidth() - shyWidth) <= 1.0);
2559 layout.endLayout();
2560 }
2561}
2562
2563void tst_QTextLayout::min_maximumWidth()
2564{
2565 QString longString("lmong_long_crazy_87235982735_23857239682376923876923876-fuwhfhfw-names-AAAA-deeaois2019-03-03.and.more");
2566 QTextLayout layout(longString, testFont);
2567
2568 for (int wrapMode = QTextOption::NoWrap; wrapMode <= QTextOption::WrapAtWordBoundaryOrAnywhere; ++wrapMode) {
2569 QTextOption opt;
2570 opt.setWrapMode((QTextOption::WrapMode)wrapMode);
2571 layout.setTextOption(opt);
2572 layout.beginLayout();
2573 while (layout.createLine().isValid()) { }
2574 layout.endLayout();
2575 const qreal minWidth = layout.minimumWidth();
2576 const qreal maxWidth = layout.maximumWidth();
2577
2578 // Try the layout from slightly wider than the widest (maxWidth)
2579 // and narrow it down to slighly narrower than minWidth
2580 // layout.maximumWidth() should return the same regardless
2581 qreal width = qCeil(v: maxWidth/10)*10 + 10; // begin a bit wider
2582 const qreal stepSize = 20;
2583 while (width >= minWidth - stepSize) {
2584 layout.beginLayout();
2585 for (;;) {
2586 QTextLine line = layout.createLine();
2587 if (!line.isValid())
2588 break;
2589 line.setLineWidth(width);
2590 }
2591 layout.endLayout();
2592 QCOMPARE(layout.minimumWidth(), minWidth);
2593 QCOMPARE(layout.maximumWidth(), maxWidth);
2594 width -= stepSize;
2595 }
2596 }
2597}
2598
2599QTEST_MAIN(tst_QTextLayout)
2600#include "tst_qtextlayout.moc"
2601

source code of qtbase/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp