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 | |
32 | #include <qtextdocument.h> |
33 | #include <qabstracttextdocumentlayout.h> |
34 | #include <qdebug.h> |
35 | #include <qpainter.h> |
36 | #include <qtexttable.h> |
37 | #ifndef QT_NO_WIDGETS |
38 | #include <qtextedit.h> |
39 | #include <qscrollbar.h> |
40 | #endif |
41 | |
42 | class tst_QTextDocumentLayout : public QObject |
43 | { |
44 | Q_OBJECT |
45 | |
46 | private slots: |
47 | void init(); |
48 | void cleanup(); |
49 | void cleanupTestCase(); |
50 | void defaultPageSizeHandling(); |
51 | void idealWidth(); |
52 | void lineSeparatorFollowingTable(); |
53 | #ifndef QT_NO_WIDGETS |
54 | void wrapAtWordBoundaryOrAnywhere(); |
55 | #endif |
56 | void inlineImage(); |
57 | void clippedTableCell(); |
58 | void floatingTablePageBreak(); |
59 | void imageAtRightAlignedTab(); |
60 | void blockVisibility(); |
61 | |
62 | void largeImage(); |
63 | |
64 | private: |
65 | QTextDocument *doc; |
66 | }; |
67 | |
68 | void tst_QTextDocumentLayout::init() |
69 | { |
70 | doc = new QTextDocument; |
71 | } |
72 | |
73 | void tst_QTextDocumentLayout::cleanup() |
74 | { |
75 | delete doc; |
76 | doc = 0; |
77 | } |
78 | |
79 | void tst_QTextDocumentLayout::cleanupTestCase() |
80 | { |
81 | if (qgetenv(varName: "QTEST_KEEP_IMAGEDATA" ).toInt() == 0) { |
82 | QFile::remove(fileName: QLatin1String("expected.png" )); |
83 | QFile::remove(fileName: QLatin1String("img.png" )); |
84 | } |
85 | } |
86 | |
87 | void tst_QTextDocumentLayout::defaultPageSizeHandling() |
88 | { |
89 | QAbstractTextDocumentLayout *layout = doc->documentLayout(); |
90 | QVERIFY(layout); |
91 | |
92 | QVERIFY(!doc->pageSize().isValid()); |
93 | QSizeF docSize = layout->documentSize(); |
94 | QVERIFY(docSize.width() > 0 && docSize.width() < 1000); |
95 | QVERIFY(docSize.height() > 0 && docSize.height() < 1000); |
96 | |
97 | doc->setPlainText("Some text\nwith a few lines\nand not real information\nor anything otherwise useful" ); |
98 | |
99 | docSize = layout->documentSize(); |
100 | QVERIFY(docSize.isValid()); |
101 | QVERIFY(docSize.width() != INT_MAX); |
102 | QVERIFY(docSize.height() != INT_MAX); |
103 | } |
104 | |
105 | void tst_QTextDocumentLayout::idealWidth() |
106 | { |
107 | doc->setPlainText("Some text\nwith a few lines\nand not real information\nor anything otherwise useful" ); |
108 | doc->setTextWidth(1000); |
109 | QCOMPARE(doc->textWidth(), qreal(1000)); |
110 | QCOMPARE(doc->size().width(), doc->textWidth()); |
111 | QVERIFY(doc->idealWidth() < doc->textWidth()); |
112 | QVERIFY(doc->idealWidth() > 0); |
113 | |
114 | QTextBlockFormat fmt; |
115 | fmt.setAlignment(Qt::AlignRight | Qt::AlignAbsolute); |
116 | QTextCursor cursor(doc); |
117 | cursor.select(selection: QTextCursor::Document); |
118 | cursor.mergeBlockFormat(modifier: fmt); |
119 | |
120 | QCOMPARE(doc->textWidth(), qreal(1000)); |
121 | QCOMPARE(doc->size().width(), doc->textWidth()); |
122 | QVERIFY(doc->idealWidth() < doc->textWidth()); |
123 | QVERIFY(doc->idealWidth() > 0); |
124 | } |
125 | |
126 | // none of the QTextLine items in the document should intersect with the margin rect |
127 | void tst_QTextDocumentLayout::lineSeparatorFollowingTable() |
128 | { |
129 | QString html_begin("<html><table border=1><tr><th>Column 1</th></tr><tr><td>Data</td></tr></table><br>" ); |
130 | QString html_text("bla bla bla bla bla bla bla bla<br>" ); |
131 | QString html_end("<table border=1><tr><th>Column 1</th></tr><tr><td>Data</td></tr></table></html>" ); |
132 | |
133 | QString html = html_begin; |
134 | |
135 | for (int i = 0; i < 80; ++i) |
136 | html += html_text; |
137 | |
138 | html += html_end; |
139 | |
140 | doc->setHtml(html); |
141 | |
142 | QTextCursor cursor(doc); |
143 | cursor.movePosition(op: QTextCursor::Start); |
144 | |
145 | const int margin = 87; |
146 | const int pageWidth = 873; |
147 | const int pageHeight = 1358; |
148 | |
149 | QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); |
150 | fmt.setMargin(margin); |
151 | doc->rootFrame()->setFrameFormat(fmt); |
152 | |
153 | QFont font(doc->defaultFont()); |
154 | font.setPointSize(10); |
155 | doc->setDefaultFont(font); |
156 | doc->setPageSize(QSizeF(pageWidth, pageHeight)); |
157 | |
158 | QRectF marginRect(QPointF(0, pageHeight - margin), QSizeF(pageWidth, 2 * margin)); |
159 | |
160 | // force layouting |
161 | doc->pageCount(); |
162 | |
163 | for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) { |
164 | QTextLayout *layout = block.layout(); |
165 | for (int i = 0; i < layout->lineCount(); ++i) { |
166 | QTextLine line = layout->lineAt(i); |
167 | QRectF rect = line.rect().translated(p: layout->position()); |
168 | QVERIFY(!rect.intersects(marginRect)); |
169 | } |
170 | } |
171 | } |
172 | |
173 | #ifndef QT_NO_WIDGETS |
174 | void tst_QTextDocumentLayout::wrapAtWordBoundaryOrAnywhere() |
175 | { |
176 | //task 150562 |
177 | QTextEdit edit; |
178 | edit.setText("<table><tr><td>hello hello hello" |
179 | "thisisabigwordthisisabigwordthisisabigwordthisisabigwordthisisabigword" |
180 | "hello hello hello</td></tr></table>" ); |
181 | edit.setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); |
182 | edit.resize(w: 100, h: 100); |
183 | edit.show(); |
184 | QVERIFY(!edit.horizontalScrollBar()->isVisible()); |
185 | } |
186 | #endif |
187 | |
188 | void tst_QTextDocumentLayout::inlineImage() |
189 | { |
190 | doc->setPageSize(QSizeF(800, 500)); |
191 | |
192 | QImage img(400, 400, QImage::Format_RGB32); |
193 | QLatin1String name("bigImage" ); |
194 | |
195 | doc->addResource(type: QTextDocument::ImageResource, name: QUrl(name), resource: img); |
196 | |
197 | QTextImageFormat imgFormat; |
198 | imgFormat.setName(name); |
199 | imgFormat.setWidth(img.width()); |
200 | |
201 | QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); |
202 | qreal height = doc->pageSize().height() - fmt.topMargin() - fmt.bottomMargin(); |
203 | imgFormat.setHeight(height); |
204 | |
205 | QTextCursor cursor(doc); |
206 | cursor.insertImage(format: imgFormat); |
207 | |
208 | QCOMPARE(doc->pageCount(), 1); |
209 | } |
210 | |
211 | void tst_QTextDocumentLayout::clippedTableCell() |
212 | { |
213 | const char *html = |
214 | "<table style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"" |
215 | "border=\"0\" margin=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr><td></td></tr></table>" ; |
216 | |
217 | doc->setHtml(html); |
218 | doc->pageSize(); |
219 | |
220 | QTextCursor cursor(doc); |
221 | cursor.movePosition(op: QTextCursor::Right); |
222 | |
223 | QTextTable *table = cursor.currentTable(); |
224 | QVERIFY(table); |
225 | |
226 | QTextCursor cellCursor = table->cellAt(row: 0, col: 0).firstCursorPosition(); |
227 | QImage src(16, 16, QImage::Format_ARGB32_Premultiplied); |
228 | src.fill(pixel: 0xffff0000); |
229 | cellCursor.insertImage(image: src); |
230 | |
231 | QTextBlock block = cellCursor.block(); |
232 | QRectF r = doc->documentLayout()->blockBoundingRect(block); |
233 | |
234 | QRectF rect(0, 0, r.left() + 1, 64); |
235 | |
236 | QImage img(64, 64, QImage::Format_ARGB32_Premultiplied); |
237 | img.fill(pixel: 0x0); |
238 | QImage expected = img; |
239 | QPainter p(&img); |
240 | doc->drawContents(painter: &p, rect); |
241 | p.end(); |
242 | p.begin(&expected); |
243 | r.setWidth(1); |
244 | p.fillRect(r, c: Qt::red); |
245 | p.end(); |
246 | |
247 | img.save(fileName: "img.png" ); |
248 | expected.save(fileName: "expected.png" ); |
249 | QCOMPARE(img, expected); |
250 | } |
251 | |
252 | void tst_QTextDocumentLayout::floatingTablePageBreak() |
253 | { |
254 | doc->clear(); |
255 | |
256 | QTextCursor cursor(doc); |
257 | |
258 | QTextTableFormat tableFormat; |
259 | tableFormat.setPosition(QTextFrameFormat::FloatLeft); |
260 | QTextTable *table = cursor.insertTable(rows: 50, cols: 1, format: tableFormat); |
261 | Q_UNUSED(table); |
262 | |
263 | // Make height of document 2/3 of the table, fitting the table into two pages |
264 | QSizeF documentSize = doc->size(); |
265 | documentSize.rheight() *= 2.0 / 3.0; |
266 | |
267 | doc->setPageSize(documentSize); |
268 | |
269 | QCOMPARE(doc->pageCount(), 2); |
270 | } |
271 | |
272 | void tst_QTextDocumentLayout::imageAtRightAlignedTab() |
273 | { |
274 | doc->clear(); |
275 | |
276 | QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); |
277 | fmt.setMargin(0); |
278 | doc->rootFrame()->setFrameFormat(fmt); |
279 | |
280 | QTextCursor cursor(doc); |
281 | QTextBlockFormat blockFormat; |
282 | QList<QTextOption::Tab> tabs; |
283 | QTextOption::Tab tab; |
284 | tab.position = 300; |
285 | tab.type = QTextOption::RightTab; |
286 | tabs.append(t: tab); |
287 | blockFormat.setTabPositions(tabs); |
288 | |
289 | // First block: text, some of it right-aligned |
290 | cursor.insertBlock(format: blockFormat); |
291 | cursor.insertText(text: "first line\t" ); |
292 | cursor.insertText(text: "right-aligned text" ); |
293 | |
294 | // Second block: text, then right-aligned image |
295 | cursor.insertBlock(format: blockFormat); |
296 | cursor.insertText(text: "second line\t" ); |
297 | QImage img(48, 48, QImage::Format_RGB32); |
298 | const QString name = QString::fromLatin1(str: "image" ); |
299 | doc->addResource(type: QTextDocument::ImageResource, name: QUrl(name), resource: img); |
300 | QTextImageFormat imgFormat; |
301 | imgFormat.setName(name); |
302 | cursor.insertImage(format: imgFormat); |
303 | |
304 | qreal bearing = QFontMetricsF(doc->defaultFont()).rightBearing(QLatin1Char('t')); |
305 | QCOMPARE(doc->idealWidth(), std::max(300.0, 300.0 - bearing)); |
306 | } |
307 | |
308 | void tst_QTextDocumentLayout::blockVisibility() |
309 | { |
310 | QTextCursor cursor(doc); |
311 | for (int i = 0; i < 10; ++i) { |
312 | if (!doc->isEmpty()) |
313 | cursor.insertBlock(); |
314 | cursor.insertText(text: "A" ); |
315 | } |
316 | |
317 | qreal margin = doc->documentMargin(); |
318 | QSizeF emptySize(2 * margin, 2 * margin); |
319 | QSizeF halfSize = doc->size(); |
320 | halfSize.rheight() -= 2 * margin; |
321 | halfSize.rheight() /= 2; |
322 | halfSize.rheight() += 2 * margin; |
323 | |
324 | for (int i = 0; i < 10; i += 2) { |
325 | QTextBlock block = doc->findBlockByNumber(blockNumber: i); |
326 | block.setVisible(false); |
327 | doc->markContentsDirty(from: block.position(), length: block.length()); |
328 | } |
329 | |
330 | QCOMPARE(doc->size(), halfSize); |
331 | |
332 | for (int i = 1; i < 10; i += 2) { |
333 | QTextBlock block = doc->findBlockByNumber(blockNumber: i); |
334 | block.setVisible(false); |
335 | doc->markContentsDirty(from: block.position(), length: block.length()); |
336 | } |
337 | |
338 | QCOMPARE(doc->size(), emptySize); |
339 | |
340 | for (int i = 0; i < 10; i += 2) { |
341 | QTextBlock block = doc->findBlockByNumber(blockNumber: i); |
342 | block.setVisible(true); |
343 | doc->markContentsDirty(from: block.position(), length: block.length()); |
344 | } |
345 | |
346 | QCOMPARE(doc->size(), halfSize); |
347 | } |
348 | |
349 | void tst_QTextDocumentLayout::largeImage() |
350 | { |
351 | auto img = QImage(400, 500, QImage::Format_ARGB32_Premultiplied); |
352 | img.fill(color: Qt::black); |
353 | |
354 | { |
355 | QTextDocument document; |
356 | |
357 | document.addResource(type: QTextDocument::ImageResource, |
358 | name: QUrl("data://test.png" ), resource: QVariant(img)); |
359 | document.setPageSize({500, 504}); |
360 | |
361 | auto html = "<img src=\"data://test.png\">" ; |
362 | document.setHtml(html); |
363 | |
364 | QCOMPARE(document.pageCount(), 2); |
365 | } |
366 | |
367 | { |
368 | QTextDocument document; |
369 | |
370 | document.addResource(type: QTextDocument::ImageResource, |
371 | name: QUrl("data://test.png" ), resource: QVariant(img)); |
372 | document.setPageSize({500, 508}); |
373 | |
374 | auto html = "<img src=\"data://test.png\">" ; |
375 | document.setHtml(html); |
376 | |
377 | QCOMPARE(document.pageCount(), 1); |
378 | } |
379 | |
380 | { |
381 | QTextDocument document; |
382 | |
383 | document.addResource(type: QTextDocument::ImageResource, |
384 | name: QUrl("data://test.png" ), resource: QVariant(img)); |
385 | document.setPageSize({585, 250}); |
386 | |
387 | auto html = "<img src=\"data://test.png\">" ; |
388 | document.setHtml(html); |
389 | |
390 | QCOMPARE(document.pageCount(), 3); |
391 | } |
392 | |
393 | { |
394 | QTextDocument document; |
395 | |
396 | document.addResource(type: QTextDocument::ImageResource, |
397 | name: QUrl("data://test.png" ), resource: QVariant(img)); |
398 | document.setPageSize({585, 258}); |
399 | |
400 | auto html = "<img src=\"data://test.png\">" ; |
401 | document.setHtml(html); |
402 | |
403 | QCOMPARE(document.pageCount(), 2); |
404 | } |
405 | } |
406 | |
407 | QTEST_MAIN(tst_QTextDocumentLayout) |
408 | #include "tst_qtextdocumentlayout.moc" |
409 | |