1 /*
2  *  This file is part of Calligra tests
3  *
4  *  Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 #include "TestDocumentLayout.h"
21 
22 #include <KoParagraphStyle.h>
23 #include <KoListStyle.h>
24 #include <KoListLevelProperties.h>
25 #include <KoStyleManager.h>
26 #include <KoTextBlockData.h>
27 #include <KoTextBlockBorderData.h>
28 #include <KoTextDocument.h>
29 
30 #include <QtGui>
31 
32 #include <QDebug>
33 
34 #define FRAME_SPACING 10.0
35 
36 
initTestCase()37 void TestDocumentLayout::initTestCase()
38 {
39     m_shape1 = 0;
40     m_doc = 0;
41     m_layout = 0;
42 
43     m_loremIpsum = QString("Lorem ipsum dolor sit amet, XgXgectetuer adiXiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.");
44 }
45 
initForNewTest(const QString & initText)46 void TestDocumentLayout::initForNewTest(const QString &initText)
47 {
48     // this leaks memory like mad, but who cares ;)
49     m_shape1 = new MockTextShape();
50     m_shape1->setSize(QSizeF(200, 1000));
51 
52     // this leaks memory like mad, but who cares ;)
53     m_layout = m_shape1->layout;
54     Q_ASSERT(m_layout);
55     m_doc = m_layout->document();
56     Q_ASSERT(m_doc);
57     m_doc->setDefaultFont(QFont("Sans Serif", 12, QFont::Normal, false)); //do it manually since we do not load the appDefaultStyle
58     m_textLayout = new TextShapeLayout(m_layout);
59     m_layout->setLayout(m_textLayout);
60     m_styleManager = new KoStyleManager();
61     KoTextDocument(m_doc).setStyleManager(m_styleManager);
62 
63     m_block = m_doc->begin();
64     if (initText.length() > 0) {
65         QTextCursor cursor(m_doc);
66         cursor.insertText(initText);
67         KoParagraphStyle style;
68         style.setStyleId(101); // needed to do manually since we don't use the stylemanager
69         QTextBlock b2 = m_doc->begin();
70         while (b2.isValid()) {
71             style.applyStyle(b2);
72             b2 = b2.next();
73         }
74     }
75 }
76 
testLineBreaking()77 void TestDocumentLayout::testLineBreaking()
78 {
79     initForNewTest(m_loremIpsum);
80     m_layout->layout();
81     QTextLayout *blockLayout = m_block.layout();
82 
83     //QCOMPARE(blockLayout->lineCount(), 16);
84     QCOMPARE(blockLayout->lineForTextPosition(1).width(), 200.0);
85 }
86 
testMultiFrameLineBreaking()87 void TestDocumentLayout::testMultiFrameLineBreaking()
88 {
89     initForNewTest(m_loremIpsum);
90     m_shape1->setSize(QSizeF(200, 47)); // fits 3 lines.
91     KoShape *shape2 = new MockTextShape();
92     shape2->setSize(QSizeF(120, 1000));
93     m_layout->addShape(shape2);
94 
95     m_layout->layout();
96     QTextLayout *blockLayout = m_block.layout();
97     QCOMPARE(blockLayout->lineAt(0).width(), 200.0);
98     QCOMPARE(blockLayout->lineAt(1).width(), 200.0);
99     QCOMPARE(blockLayout->lineAt(2).width(), 200.0);
100     QCOMPARE(blockLayout->lineAt(3).width(), 120.0);
101     QCOMPARE(blockLayout->lineAt(4).width(), 120.0);
102 
103     QTextLine line = blockLayout->lineAt(3);
104     QVERIFY(line.y() > m_shape1->size().height()); // it has to be outside of m_shape1
105     const qreal topOfFrame2 = line.y();
106     line = blockLayout->lineAt(4);
107     //qDebug() << line.y() - topOfFrame2 - 14.4;
108     QVERIFY(qAbs(line.y() - topOfFrame2 - 14.4) < 0.125);
109 }
110 
testBasicLineSpacing()111 void TestDocumentLayout::testBasicLineSpacing()
112 {
113     /// Tests incrementing Y pos based on the font size
114     initForNewTest(m_loremIpsum);
115     QTextCursor cursor(m_doc);
116     cursor.setPosition(0);
117     cursor.setPosition(m_loremIpsum.length() - 1, QTextCursor::KeepAnchor);
118     QTextCharFormat charFormat = cursor.charFormat();
119     charFormat.setFontPointSize(12);
120     cursor.mergeCharFormat(charFormat);
121     m_layout->layout();
122     QTextLayout *blockLayout = m_block.layout();
123 
124     const qreal fontHeight12 = 12;
125     qreal lineSpacing12 = fontHeight12 * 1.2; // 120% is the normal lineSpacing.
126     const qreal fontHeight18 = 18;
127     qreal lineSpacing18 = fontHeight18 * 1.2; // 120% is the normal lineSpacing.
128 
129     // QCOMPARE(blockLayout->lineCount(), 15);
130     QCOMPARE(blockLayout->lineForTextPosition(1).width(), 200.0);
131     QTextLine line;
132     for (int i = 0; i < 15; i++) {
133         line = blockLayout->lineAt(i);
134         QVERIFY(line.isValid());
135         // The reason for this weird check is that the values are stored internally
136         // as 26.6 fixed point integers. The entire internal text layout is
137         // actually done using fixed point arithmetic. This is due to embedded
138         // considerations, and offers general performance benefits across all
139         // platforms.
140         //qDebug() << i << qAbs(line.y() - i * lineSpacing12);
141         QVERIFY(qAbs(line.y() - i * lineSpacing12) < ROUNDING);
142     }
143 
144     // make first word smaller, should have zero effect on lineSpacing.
145     cursor.setPosition(0);
146     cursor.setPosition(11, QTextCursor::KeepAnchor);
147     charFormat.setFontPointSize(10);
148     cursor.mergeCharFormat(charFormat);
149     m_layout->layout();
150     for (int i = 0; i < 15; i++) {
151         line = blockLayout->lineAt(i);
152         QVERIFY(line.isValid());
153         //qDebug() << i << qAbs(line.y() - i * lineSpacing12);
154         QVERIFY(qAbs(line.y() - i * lineSpacing12) < ROUNDING);
155     }
156 
157     // make first word on second line word bigger, should move that line down a little.
158     int pos = blockLayout->lineAt(1).textStart();
159     cursor.setPosition(pos);
160     cursor.setPosition(pos + 12, QTextCursor::KeepAnchor);
161     charFormat.setFontPointSize(18);
162     cursor.mergeCharFormat(charFormat);
163     m_layout->layout();
164     line = blockLayout->lineAt(0);
165     QCOMPARE(line.y(), 0.0);
166     line = blockLayout->lineAt(1);
167     QVERIFY(qAbs(line.y() - lineSpacing12) < ROUNDING);
168 
169     for (int i = 2; i < 15; i++) {
170         line = blockLayout->lineAt(i);
171 //qDebug() << "i: " << i << " gives: " << line.y() << " + " <<  line.ascent() << ", " << line.descent() << " = " << line.height();
172         QVERIFY(qAbs(line.y() - (lineSpacing12 + lineSpacing18 + (i - 2) * lineSpacing12)) < ROUNDING);
173     }
174 // Test widget to show what we have
175     /*
176         class Widget : public QWidget {
177           public:
178             Widget(KWTextDocumentLayout *layout) {
179                 m_layout = layout;
180             }
181             void paintEvent (QPaintEvent * e) {
182                 QPainter painter( this );
183                 QAbstractTextDocumentLayout::PaintContext pc;
184                 pc.cursorPosition = -1;
185                 m_layout->draw( &painter, pc);
186             }
187           private:
188             KWTextDocumentLayout *m_layout;
189         };
190 
191         QMainWindow mw;
192         mw.setCentralWidget(new Widget(m_layout));
193         mw.show();
194         m_app->exec(); */
195 }
196 
testBasicLineSpacing2()197 void TestDocumentLayout::testBasicLineSpacing2()
198 {
199     initForNewTest(m_loremIpsum);
200     QTextCursor cursor(m_doc);
201     cursor.insertText("foo\n\n"); // insert empty parag;
202 
203     m_layout->layout();
204     QTextBlock block = m_doc->begin().next();
205     QTextLayout *blockLayout = block.layout();
206     QVERIFY(block.isValid());
207     blockLayout = block.layout();
208     QCOMPARE(blockLayout->lineCount(), 1);
209 
210     block = block.next();
211     QVERIFY(block.isValid());
212     blockLayout = block.layout();
213     //qDebug() << blockLayout->lineAt(0).y();
214     QVERIFY(qAbs(blockLayout->lineAt(0).y() - 28.8) < ROUNDING);
215 }
216 
testAdvancedLineSpacing()217 void TestDocumentLayout::testAdvancedLineSpacing()
218 {
219     initForNewTest("Line1\nLine2\nLine3\nLine4\nLine5\nLine6\nLine7");
220     QTextCursor cursor(m_doc);
221 
222     KoParagraphStyle style;
223     style.setLineHeightPercent(80);
224     QTextBlock block = m_doc->begin();
225     style.applyStyle(block);
226 
227     // check if styles do their work ;)
228     QCOMPARE(block.blockFormat().doubleProperty(KoParagraphStyle::PercentLineHeight), 80.0);
229 
230     block = block.next();
231     QVERIFY(block.isValid()); //line2
232     style.setLineHeightAbsolute(28.0); // removes the percentage
233     style.applyStyle(block);
234 
235     block = block.next();
236     QVERIFY(block.isValid()); // line3
237     style.setMinimumLineHeight(40.0);
238     style.setLineHeightPercent(120);
239     style.applyStyle(block);
240 
241     block = block.next();
242     QVERIFY(block.isValid()); // line4
243     style.remove(KoParagraphStyle::FixedLineHeight);
244     style.setMinimumLineHeight(5.0);
245     style.applyStyle(block);
246 
247     block = block.next();
248     QVERIFY(block.isValid()); // line5
249     style.setMinimumLineHeight(0.0);
250     style.setLineSpacing(8.0);
251     style.remove(KoParagraphStyle::PercentLineHeight);
252     style.applyStyle(block);
253 
254     block = block.next();
255     QVERIFY(block.isValid()); // line6
256     style.setLineSpacingFromFont(true);
257     style.setLineHeightPercent(100);
258     style.remove(KoParagraphStyle::LineSpacing);
259     style.applyStyle(block);
260 
261     m_layout->layout();
262     QTextLayout *blockLayout = m_block.layout();
263     QCOMPARE(blockLayout->lineAt(0).y(), 0.0);
264     block = m_doc->begin().next(); // line2
265     QVERIFY(block.isValid());
266     blockLayout = block.layout();
267     //qDebug() << blockLayout->lineAt(0).y();
268     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (12.0 * 0.8)) < ROUNDING);
269     block = block.next(); // line3
270     QVERIFY(block.isValid());
271     blockLayout = block.layout();
272     //qDebug() << blockLayout->lineAt(0).y();
273     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (9.6 + 28.0)) < ROUNDING);
274     block = block.next(); // line4
275     QVERIFY(block.isValid());
276     blockLayout = block.layout();
277     //qDebug() << blockLayout->lineAt(0).y();
278     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (37.6 + 40)) < ROUNDING);
279     block = block.next(); // line5
280     QVERIFY(block.isValid());
281     blockLayout = block.layout();
282     // qDebug() << blockLayout->lineAt(0).y();
283     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (77.6 + qMax(qreal(12 * 1.2), qreal(5.0)))) < ROUNDING); // 92
284     block = block.next(); // line6
285     QVERIFY(block.isValid());
286     blockLayout = block.layout();
287     //qDebug() << blockLayout->lineAt(0).y();
288     QCOMPARE(blockLayout->lineAt(0).y(), 92.0 + 12 + 8);
289 
290     qreal height = blockLayout->lineAt(0).height();
291     block = block.next(); // line 7
292     QVERIFY(block.isValid());
293     blockLayout = block.layout();
294     //qDebug() << blockLayout->lineAt(0).y();
295     QCOMPARE(blockLayout->lineAt(0).y(), 112 + height);
296 }
297 
testMargins()298 void TestDocumentLayout::testMargins()
299 {
300     initForNewTest(m_loremIpsum);
301     QTextCursor cursor(m_doc);
302     QTextBlockFormat bf = cursor.blockFormat();
303     bf.setLeftMargin(10.0);
304     cursor.setBlockFormat(bf);
305     m_layout->layout();
306     QTextLayout *blockLayout = m_block.layout();
307     QCOMPARE(blockLayout->lineAt(0).x(), 10.0);
308     QCOMPARE(blockLayout->lineAt(0).width(), 190.0);
309 
310     bf.setRightMargin(15.0);
311     cursor.setBlockFormat(bf);
312     m_layout->layout();
313     QCOMPARE(blockLayout->lineAt(0).x(), 10.0);
314     QCOMPARE(blockLayout->lineAt(0).width(), 175.0);
315 
316     bf.setLeftMargin(0.0);
317     cursor.setBlockFormat(bf);
318     m_layout->layout();
319     QCOMPARE(blockLayout->lineAt(0).x(), 0.0);
320     QCOMPARE(blockLayout->lineAt(0).width(), 185.0); // still uses the right margin of 15
321 
322     cursor.setPosition(m_loremIpsum.length());
323     cursor.insertText("\n");
324     bf.setTopMargin(12);
325     cursor.setBlockFormat(bf);
326     cursor.insertText(m_loremIpsum);// create second parag
327     m_layout->layout();
328     QCOMPARE(blockLayout->lineAt(0).x(), 0.0); // parag 1
329     QCOMPARE(blockLayout->lineAt(0).width(), 185.0);
330 
331     // and test parag 2
332     QTextBlock block2 = m_doc->begin().next();
333     QTextLayout *layout = block2.layout();
334     QCOMPARE(layout->lineAt(0).x(), 0.0);
335     QCOMPARE(layout->lineAt(0).width(), 185.0);
336 
337     QTextLine lastLineOfParag1 = blockLayout->lineAt(blockLayout->lineCount() - 1);
338     QTextLine firstLineOfParag2 =  layout->lineAt(0);
339     const qreal FONTSIZE = 12.0;
340     const qreal BottomParag1 = lastLineOfParag1.y() + (FONTSIZE * 1.2);
341     QVERIFY(qAbs(firstLineOfParag2.y() - BottomParag1  - 12.0) < ROUNDING);
342 }
343 
testMultipageMargins()344 void TestDocumentLayout::testMultipageMargins()
345 {
346     initForNewTest("123456789\nparagraph 2\nlksdjflksdjflksdjlkfjsdlkfjsdlk sldkfj lsdkjf lskdjf lsd lfsjd lfk");
347     QTextCursor cursor(m_doc);
348 
349     KoParagraphStyle h1;
350     h1.setTopMargin(100.0);
351     h1.setBottomMargin(20.0);
352     m_styleManager->add(&h1);
353 
354     QTextBlock block = m_doc->begin();
355     h1.applyStyle(block);
356     block = block.next();
357     h1.applyStyle(block);
358 
359     m_shape1->setSize(QSizeF(200, 14.4 + 100 + 20 + 14.4 + 5)); // 5 for fun..
360     KoShape *shape2 = new MockTextShape();
361     shape2->setSize(QSizeF(120, 1000));
362     m_layout->addShape(shape2);
363 
364     m_layout->layout();
365 
366     /* The above has 3 parags with a very tall top margin and a bottom margin
367      * The first line is expected to be displayed at top of the page because we
368      * never show the topMargin on new pages (see also testParagOffset).
369      * The second parag will then be 100 + 20 points lower.
370      * The shape will not have enough space for the bottom margin of this second parag,
371      * but that does not move it to the second shape!
372      * The 3th parag is then displayed at the top of the second shape.
373      */
374 
375     block = m_doc->begin();
376     QVERIFY(block.isValid());
377     QTextLayout *blockLayout = block.layout(); // first parag
378     QCOMPARE(blockLayout->lineAt(0).y(), 0.0);
379     block = block.next();
380     QVERIFY(block.isValid());
381     blockLayout = block.layout(); // second parag
382     //qDebug() << blockLayout->lineAt(0).y() << "=" << (12.0 * 1.2 + 100.0 + 20.0);
383     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (12.0 * 1.2 + 100.0 + 20.0)) < ROUNDING);
384     block = block.next();
385     QVERIFY(block.isValid());
386     blockLayout = block.layout(); // thirth parag
387     // the 10 in the next line is hardcoded distance between frames.
388     //qDebug() << blockLayout->lineAt(0).y() << "=" << m_shape1->size().height() + FRAME_SPACING;
389     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (m_shape1->size().height() + FRAME_SPACING)) < ROUNDING);
390 
391     /* TODO
392         - top margin at new page is honoured when the style used has a
393          'page break before' set to true.
394      */
395 }
396 
testTextIndent()397 void TestDocumentLayout::testTextIndent()
398 {
399     initForNewTest(m_loremIpsum);
400     QTextCursor cursor(m_doc);
401     QTextBlockFormat format = cursor.blockFormat();
402     format.setTextIndent(20);
403     cursor.setBlockFormat(format);
404 
405     m_layout->layout();
406     QTextLayout *blockLayout = m_block.layout();
407 
408     QCOMPARE(blockLayout->lineAt(0).x(), 20.0);
409     QCOMPARE(blockLayout->lineAt(1).x(), 0.0);
410 }
411 
testBasicTextAlignments()412 void TestDocumentLayout::testBasicTextAlignments()
413 {
414     initForNewTest("Left\nCenter\nRight");
415 
416     QTextCursor cursor(m_doc);
417     QTextBlockFormat format = cursor.blockFormat();
418     format.setAlignment(Qt::AlignLeft);
419     cursor.setBlockFormat(format);
420     cursor.setPosition(6);
421     format.setAlignment(Qt::AlignHCenter);
422     cursor.setBlockFormat(format);
423     cursor.setPosition(13);
424     format.setAlignment(Qt::AlignRight);
425     cursor.setBlockFormat(format);
426 
427     m_layout->layout();
428     QTextLayout *blockLayout = m_block.layout();
429     QCOMPARE(blockLayout->lineAt(0).x(), 0.0);
430 
431     QTextBlock block = m_doc->begin().next();
432     QVERIFY(block.isValid());
433     blockLayout = block.layout();
434 
435     QRectF rect = blockLayout->lineAt(0).naturalTextRect();
436     QVERIFY(rect.x() > 60);
437     QCOMPARE(rect.x() + rect.width() + (200 - rect.right()), 200.0);
438     block = block.next();
439     QVERIFY(block.isValid());
440     blockLayout = block.layout();
441     rect = blockLayout->lineAt(0).naturalTextRect();
442     QVERIFY(rect.x() > 150);
443     QVERIFY(rect.right() >= 200.0);
444 }
445 
testTextAlignments()446 void TestDocumentLayout::testTextAlignments()
447 {
448     // TODO justified & justified, last line
449     initForNewTest("Left\nRight\nﺵﻻﺆﻴﺜﺒ\nﺵﻻﺆﻴﺜﺒ\nLast Line.");
450     KoParagraphStyle start;
451     start.setAlignment(Qt::AlignLeading);
452     KoParagraphStyle end;
453     end.setAlignment(Qt::AlignTrailing);
454 
455     KoParagraphStyle startRTL;
456     startRTL.setAlignment(Qt::AlignLeading);
457     startRTL.setTextProgressionDirection(KoText::RightLeftTopBottom);
458     KoParagraphStyle endRTL;
459     endRTL.setAlignment(Qt::AlignTrailing);
460     endRTL.setTextProgressionDirection(KoText::RightLeftTopBottom);
461 
462     QTextBlock block = m_doc->begin();
463     start.applyStyle(block);
464     block = block.next();
465     end.applyStyle(block);
466     block = block.next();
467     startRTL.applyStyle(block);
468     block = block.next();
469     endRTL.applyStyle(block);
470     block = block.next();
471     endRTL.applyStyle(block);
472 
473     m_layout->layout();
474     QTextLayout *blockLayout = m_block.layout();
475 
476     // line 'Left'
477     QRectF rect = blockLayout->lineAt(0).naturalTextRect();
478     QCOMPARE(rect.x(), 0.);
479 
480     // line 'Right'
481     block = m_doc->begin().next();
482     rect = block.layout()->lineAt(0).naturalTextRect();
483     QVERIFY(rect.right() - 200 <= 1);
484     QVERIFY(rect.left() > 0.);
485 
486     // line with align Leading and RTL progression
487     block = block.next();
488     rect = block.layout()->lineAt(0).naturalTextRect();
489     QVERIFY(rect.right() - 200 <= 1);
490     QVERIFY(rect.left() > 0.); // expect right alignment
491 
492     // line with align tailing and RTL progression
493     block = block.next();
494     rect = block.layout()->lineAt(0).naturalTextRect();
495     QCOMPARE(rect.x(), 0.); // expect left alignment
496 
497     // non RTL _text_ but RTL progression as well as align trailing
498     block = block.next();
499     rect = block.layout()->lineAt(0).naturalTextRect();
500     QCOMPARE(rect.x(), 0.); // expect left alignment
501     // TODO can we check if the dot is the left most painted char?
502 }
503 
testPageBreak()504 void TestDocumentLayout::testPageBreak()
505 {
506     initForNewTest("line\nParag2\nSimple Parag\nLast");
507     KoParagraphStyle style;
508     style.setBreakBefore(true);
509     style.setBreakAfter(true);
510     QTextBlock block = m_doc->begin();
511     style.applyStyle(block); // break before Line (ignored) and after, moving Parag2 to a new shape
512     block = block.next();
513     QVERIFY(block.isValid());
514     block = block.next();
515     QVERIFY(block.isValid());
516     style.setBreakBefore(false); // break after 'simple parag' moving 'Last' to a new shape
517     style.setBreakAfter(true);
518     style.applyStyle(block);
519 
520     m_shape1->setSize(QSizeF(200, 40));
521     KoShape *shape2 = new MockTextShape();
522     shape2->setSize(QSizeF(200, 100));
523     m_layout->addShape(shape2);
524 
525     KoShape *shape3 = new MockTextShape();
526     shape3->setSize(QSizeF(200, 100));
527     m_layout->addShape(shape3);
528 
529     m_layout->layout();
530     QTextLayout *blockLayout = m_block.layout();
531 
532     QCOMPARE(blockLayout->lineAt(0).y(), 0.0);
533     block = m_doc->begin().next();
534     QVERIFY(block.isValid());
535     blockLayout = block.layout(); // parag 2
536     QCOMPARE(blockLayout->lineCount(), 1);
537     QCOMPARE(blockLayout->lineAt(0).y(), 50.0);
538     block = block.next();
539     QVERIFY(block.isValid());
540     blockLayout = block.layout(); // parag 3
541     //qDebug() << qAbs(blockLayout->lineAt(0).y());
542     QVERIFY(qAbs(blockLayout->lineAt(0).y() - 64.4) < ROUNDING);
543     block = block.next();
544     QVERIFY(block.isValid());
545     blockLayout = block.layout(); // parag 4
546     QCOMPARE(blockLayout->lineCount(), 1);
547     QCOMPARE(blockLayout->lineAt(0).y(), 160.0);
548 }
549 
testPageBreak2()550 void TestDocumentLayout::testPageBreak2()
551 {
552     initForNewTest("line\nParag2\nSimple Parag\nLast");
553     QTextBlock block = m_doc->begin();
554     QTextCursor cursor(block);
555     QTextBlockFormat bf = cursor.blockFormat();
556     bf.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter);
557 //bf.setPageBreakPolicy(PageBreak_AlwaysBefore);
558     cursor.setBlockFormat(bf);
559     block = block.next();
560     QVERIFY(block.isValid());
561     block = block.next();
562     QVERIFY(block.isValid());
563     cursor = QTextCursor(block);
564     bf.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter);
565     cursor.setBlockFormat(bf);
566 
567     m_shape1->setSize(QSizeF(200, 40));
568     KoShape *shape2 = new MockTextShape();
569     shape2->setSize(QSizeF(200, 100));
570     m_layout->addShape(shape2);
571     KoShape *shape3 = new MockTextShape();
572     shape3->setSize(QSizeF(200, 100));
573     m_layout->addShape(shape3);
574 
575     m_layout->layout();
576     QTextLayout *blockLayout = m_block.layout();
577 
578     QCOMPARE(blockLayout->lineAt(0).y(), 0.0);
579     block = m_doc->begin().next();
580     QVERIFY(block.isValid());
581     blockLayout = block.layout(); // parag 2
582     QCOMPARE(blockLayout->lineCount(), 1);
583     QCOMPARE(blockLayout->lineAt(0).y(), 50.0);
584     block = block.next();
585     QVERIFY(block.isValid());
586     blockLayout = block.layout(); // parag 3
587     //qDebug() << qAbs(blockLayout->lineAt(0).y());
588     QVERIFY(qAbs(blockLayout->lineAt(0).y() - 64.4) < ROUNDING);
589     block = block.next();
590     QVERIFY(block.isValid());
591     blockLayout = block.layout(); // parag 4
592     QCOMPARE(blockLayout->lineCount(), 1);
593     QCOMPARE(blockLayout->lineAt(0).y(), 160.0);
594 }
595 
testParagOffset()596 void TestDocumentLayout::testParagOffset()
597 {
598     initForNewTest("First line\nSecond line\n");
599 
600     /*
601       Test for top-of-page indent.
602       When text gets moved to the next page the indent above the paragraph should be ignored
603       since the moving to the next page is already enough whitespace.
604       The user might want to have spacing at the top of the page, though, without using manually
605       typed enters.
606       There are 2 cases when such spacing is honoured.
607       1) When the paragraph has an offset defined in its style as well as a 'break before' property set.
608       2) When its in the paragraph-properties, but not in the style (i.e. a manual override)
609     */
610 
611     KoParagraphStyle h1;
612     h1.setTopMargin(20);
613     h1.setBreakBefore(true);
614     m_styleManager->add(&h1);
615 
616     QTextBlock block = m_doc->begin();
617     h1.applyStyle(block);
618     block = block.next();
619     h1.applyStyle(block);
620 
621     m_shape1->setSize(QSizeF(200, 100));
622     KoShape *shape2 = new MockTextShape();
623     shape2->setSize(QSizeF(200, 100));
624     m_layout->addShape(shape2);
625 
626     // 1)
627     m_layout->layout();
628     block = m_doc->begin();
629     QTextLayout *blockLayout = m_block.layout();
630     QCOMPARE(blockLayout->lineAt(0).y(), 20.0);
631     block = block.next();
632     QVERIFY(block.isValid());
633     blockLayout = block.layout(); // page 2
634     QCOMPARE(blockLayout->lineAt(0).y(), 130.0);
635 
636     QTextCursor cursor(m_doc->begin());
637     QTextBlockFormat bf = cursor.blockFormat();
638     cursor.setPosition(0);
639     cursor.setPosition(20, QTextCursor::KeepAnchor);
640     bf.setTopMargin(30);
641     cursor.setBlockFormat(bf);
642 
643     // 2)
644     m_layout->layout();
645     block = m_doc->begin();
646     blockLayout = block.layout(); // page 1
647     QCOMPARE(blockLayout->lineAt(0).y(), 30.0);
648     block = block.next();
649     QVERIFY(block.isValid());
650     blockLayout = block.layout(); // page 2
651     QCOMPARE(blockLayout->lineAt(0).y(), 140.0);
652 }
653 
testParagraphBorders()654 void TestDocumentLayout::testParagraphBorders()
655 {
656     initForNewTest("Paragraph with Borders\nAnother parag\n");
657     QTextCursor cursor(m_doc->begin());
658     QTextBlockFormat bf = cursor.blockFormat();
659     bf.setProperty(KoParagraphStyle::LeftBorderStyle, KoBorder::BorderSolid);
660     bf.setProperty(KoParagraphStyle::TopBorderStyle, KoBorder::BorderSolid);
661     bf.setProperty(KoParagraphStyle::BottomBorderStyle, KoBorder::BorderSolid);
662     bf.setProperty(KoParagraphStyle::RightBorderStyle, KoBorder::BorderSolid);
663     bf.setProperty(KoParagraphStyle::LeftBorderWidth, 8.0);
664     bf.setProperty(KoParagraphStyle::TopBorderWidth, 9.0);
665     bf.setProperty(KoParagraphStyle::BottomBorderWidth, 10.0);
666     bf.setProperty(KoParagraphStyle::RightBorderWidth, 11.0);
667     cursor.setBlockFormat(bf);
668 
669     m_layout->layout();
670     QTextBlock block = m_doc->begin();
671     QTextLayout *blockLayout = block.layout();
672     QCOMPARE(blockLayout->lineAt(0).x(), 8.0);
673     QCOMPARE(blockLayout->lineAt(0).y(), 9.0);
674     QCOMPARE(blockLayout->lineAt(0).width(), 200.0 - 8.0 - 11.0);
675     block = block.next();
676     blockLayout = block.layout();
677     //qDebug() << blockLayout->lineAt(0).y();
678     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (9.0 + 14.4 + 10)) < ROUNDING);  // 14.4 is 12 pt font + 20% linespacing
679 
680     // borders + padding create the total inset.
681     bf.setProperty(KoParagraphStyle::LeftPadding, 5.0);
682     bf.setProperty(KoParagraphStyle::RightPadding, 5.0);
683     bf.setProperty(KoParagraphStyle::TopPadding, 5.0);
684     bf.setProperty(KoParagraphStyle::BottomPadding, 5.0);
685     cursor.setBlockFormat(bf);
686 
687     m_layout->layout();
688     block = m_doc->begin();
689     blockLayout = block.layout();
690     QCOMPARE(blockLayout->lineAt(0).x(), 13.0);
691     QCOMPARE(blockLayout->lineAt(0).y(), 14.0);
692     QCOMPARE(blockLayout->lineAt(0).width(), 200.0 - 8.0 - 11.0 - 5.0 * 2);
693     block = block.next();
694     blockLayout = block.layout();
695     //qDebug() << blockLayout->lineAt(0).y();
696     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (9.0 + 14.4 + 10 + 5.0 * 2)) < ROUNDING);
697 
698     // borders are positioned outside the padding, lets check that to be the case.
699     block = m_doc->begin();
700     KoTextBlockData *data  = dynamic_cast<KoTextBlockData*>(block.userData());
701     QVERIFY(data);
702     KoTextBlockBorderData *border = data->border();
703     QVERIFY(border);
704     QCOMPARE(border->hasBorders(), true);
705     QRectF borderOutline = border->rect();
706     QCOMPARE(borderOutline.top(), 0.);
707     QCOMPARE(borderOutline.left(), 0.);
708     QCOMPARE(borderOutline.right(), 200.);
709 
710     // qreal borders.  Specify an additional width for each side.
711     bf.setProperty(KoParagraphStyle::LeftBorderStyle, KoBorder::BorderDouble);
712     bf.setProperty(KoParagraphStyle::TopBorderStyle, KoBorder::BorderDouble);
713     bf.setProperty(KoParagraphStyle::BottomBorderStyle, KoBorder::BorderDouble);
714     bf.setProperty(KoParagraphStyle::RightBorderStyle, KoBorder::BorderDouble);
715     bf.setProperty(KoParagraphStyle::LeftInnerBorderWidth, 2.0);
716     bf.setProperty(KoParagraphStyle::RightInnerBorderWidth, 2.0);
717     bf.setProperty(KoParagraphStyle::BottomInnerBorderWidth, 2.0);
718     bf.setProperty(KoParagraphStyle::TopInnerBorderWidth, 2.0);
719     cursor.setBlockFormat(bf);
720 
721     m_layout->layout();
722     block = m_doc->begin();
723     blockLayout = block.layout();
724     QCOMPARE(blockLayout->lineAt(0).x(), 15.0);
725     QCOMPARE(blockLayout->lineAt(0).y(), 16.0);
726     QCOMPARE(blockLayout->lineAt(0).width(), 200.0 - 8.0 - 11.0 - (5.0 + 2.0) * 2);
727     block = block.next();
728     blockLayout = block.layout();
729     //qDebug() << blockLayout->lineAt(0).y();
730     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (9.0 + 14.4 + 10 + (5.0 + 2.0) * 2)) < ROUNDING);
731 
732     // and last, make the 2 qreal border have a blank space in the middle.
733     bf.setProperty(KoParagraphStyle::LeftBorderSpacing, 3.0);
734     bf.setProperty(KoParagraphStyle::RightBorderSpacing, 3.0);
735     bf.setProperty(KoParagraphStyle::BottomBorderSpacing, 3.0);
736     bf.setProperty(KoParagraphStyle::TopBorderSpacing, 3.0);
737     cursor.setBlockFormat(bf);
738 
739     m_layout->layout();
740     block = m_doc->begin();
741     blockLayout = block.layout();
742     QCOMPARE(blockLayout->lineAt(0).x(), 18.0);
743     QCOMPARE(blockLayout->lineAt(0).y(), 19.0);
744     QCOMPARE(blockLayout->lineAt(0).width(), 200.0 - 8.0 - 11.0 - (5.0 + 2.0 + 3.0) * 2);
745     block = block.next();
746     blockLayout = block.layout();
747     //qDebug() << blockLayout->lineAt(0).y();
748     QVERIFY(qAbs(blockLayout->lineAt(0).y() - (9.0 + 14.4 + 10 + (5.0 + 2.0 + 3.0) * 2)) < ROUNDING);
749 }
750 
testBorderData()751 void TestDocumentLayout::testBorderData()
752 {
753     initForNewTest("Emtpy\nParagraph with Borders\nAnother parag\n");
754 
755     KoParagraphStyle style;
756     m_styleManager->add(&style);
757     style.setTopMargin(10);
758     KoListStyle listStyle;
759     KoListLevelProperties llp = listStyle.levelProperties(1);
760     llp.setStyle(KoListStyle::DecimalItem);
761     listStyle.setLevelProperties(llp);
762     style.setListStyle(&listStyle);
763     style.setLeftBorderWidth(3);
764 
765     QTextBlock block = m_doc->begin().next();
766     style.applyStyle(block);
767     block = block.next();
768     style.applyStyle(block);
769 
770     m_layout->layout();
771 
772     block = m_doc->begin().next();
773     KoTextBlockData *data = dynamic_cast<KoTextBlockData*>(block.userData());
774     QVERIFY(data);
775     KoTextBlockBorderData *border = data->border();
776     QVERIFY(border);
777     // 1st parag is 12 * 120% = 14.4 tall.
778     // 2nd parag starts with 10 pt offset = 24.4
779     // Hight of border is 2 parags, each 14.4 for text. Plus 10 pt above the 3th parag. = 38.8pt
780 
781     // The rules here are
782     //  * two paragraphs share a border
783     //  * The top indent (of all parags) does not have a border
784     //  * The left border is left of the counter
785     QCOMPARE(border->rect(), QRectF(0, 24.4, 188, 38.8));
786 
787     style.setBottomMargin(5);
788     // manually reapply and relayout to force immediate reaction.
789     block = m_doc->begin().next();
790     style.applyStyle(block);
791     block = block.next();
792     style.applyStyle(block);
793     m_layout->layout();
794 
795     block = m_doc->begin().next();
796     border = data->border();
797     QVERIFY(border);
798     // The tested here is
799     //  * the bottom border of the last parag is directly under the text. (so similar to rule 2)
800     // This means that the height is the prev 38.8 + the bottom of the top parag: 5pt = 43.8pt
801     QCOMPARE(border->rect(), QRectF(0, 24.4, 188, 43.8));
802 
803     QCOMPARE(data->counterPosition(), QPointF(3, 24.4));
804 
805     block = block.next();
806     data = dynamic_cast<KoTextBlockData*>(block.userData());
807     QCOMPARE(data->counterPosition(), QPointF(3, 53.8));
808 }
809 
testEmptyParag()810 void TestDocumentLayout::testEmptyParag()
811 {
812     initForNewTest("Foo\n\nBar\n");
813     m_layout->layout();
814     QTextBlock block = m_doc->begin();
815     QTextLayout *lay = block.layout();
816     QVERIFY(lay);
817     QCOMPARE(lay->lineCount(), 1);
818     const qreal y = lay->lineAt(0).position().y();
819 
820     block = block.next();
821     lay = block.layout();
822     QVERIFY(lay);
823     QCOMPARE(lay->lineCount(), 1);
824     QVERIFY(lay->lineAt(0).position().y() > y);
825     QVERIFY(qAbs(lay->lineAt(0).position().y() - 14.4) < ROUNDING);
826 }
827 
testDropCaps()828 void TestDocumentLayout::testDropCaps()
829 {
830     initForNewTest(QString("Lorem ipsum dolor sit amet, XgXgectetuer adiXiscing elit, sed diam\nsome more text")); // some not too long text so the dropcap will be bigger than the block
831 
832     KoParagraphStyle style;
833     style.setDropCaps(false);
834     style.setDropCapsLength(1);
835     style.setDropCapsLines(4);
836     style.setDropCapsDistance(9.0);
837     QTextBlock block = m_doc->begin();
838     QTextBlock secondblock = block.next();
839     style.applyStyle(block);
840 
841     m_layout->layout();
842 
843     // dummy version, caps is still false.
844     QTextLayout *blockLayout =block.layout();
845     QVERIFY(blockLayout->lineCount() > 2);
846     QTextLine line = blockLayout->lineAt(0);
847     QVERIFY(line.textLength() > 3);
848 
849     style.setDropCaps(true);
850     style.applyStyle(block);
851     m_layout->layout();
852 
853     // test that the first text line is the dropcaps and the positions are right.
854     QVERIFY(blockLayout->lineCount() > 2);
855     line = blockLayout->lineAt(0);
856     QCOMPARE(line.textLength(), 1);
857 
858     QCOMPARE(line.position().x(), 0.0);
859     QVERIFY(line.position().y() <= 0.0); // can't get a tight-boundingrect here.
860 
861     line = blockLayout->lineAt(1);
862     QVERIFY(line.textLength() > 2);
863     qreal heightNormalLine = line.height();
864     qreal linexpos = line.position().x();
865     QCOMPARE(line.position().y(), 0.0); // aligned top
866     QVERIFY(line.position().x() > 20.0); // can't get a tight-boundingrect here.
867 
868     // Now test that a following block is moved inward by the same about since
869     // it should still be influenced by the dropcap
870     blockLayout = secondblock.layout();
871     QVERIFY(blockLayout->lineCount() == 1);
872     line = blockLayout->lineAt(0);
873     QVERIFY(line.textLength() > 3);
874     QCOMPARE(line.position().x(), linexpos);
875     QVERIFY(line.position().x() > 20.0); // can't get a tight-boundingrect here.
876 
877     style.setDropCaps(false); // remove it
878     style.applyStyle(block);
879     m_layout->layout();
880     blockLayout = block.layout();
881 
882     // test that the first text line is no longer dropcaps
883     QVERIFY(blockLayout->lineCount() > 2);
884     line = blockLayout->lineAt(0);
885     QVERIFY(line.textLength() > 1);
886     QCOMPARE(line.height(), heightNormalLine);
887 }
888 
testNonBreakableLines()889 void TestDocumentLayout::testNonBreakableLines()
890 {
891     initForNewTest(m_loremIpsum.left(97) + '\n' + m_loremIpsum);
892     QTextBlock block = m_doc->begin().next();
893     QTextCursor cursor(block);
894     QTextBlockFormat format = cursor.blockFormat();
895     format.setNonBreakableLines(true);
896     cursor.setBlockFormat(format);
897 
898     m_shape1->setSize(QSizeF(200, 100));
899     KoShape *shape2 = new MockTextShape();
900     shape2->setSize(QSizeF(120, 1000));
901     m_layout->addShape(shape2);
902 
903     m_layout->layout();
904 
905     block = m_doc->begin();
906     QTextLayout *l = block.layout();
907     // make sure parag1 is in shape 1.
908     for (int i = 0; i < l->lineCount(); i++)
909         QVERIFY(l->lineAt(i).y() < 100.);
910 
911     block = block.next();
912     l = block.layout();
913     QCOMPARE(l->lineAt(0).y(), 110.);
914 }
915 
testShapePosition()916 void TestDocumentLayout::testShapePosition()
917 {
918     initForNewTest("line\nParag2\nSimple Parag\nLast");
919 
920     m_shape1->setSize(QSizeF(200, 40));
921     MockTextShape *shape2 = new MockTextShape();
922     shape2->setSize(QSizeF(200, 100));
923     m_layout->addShape(shape2);
924 
925     m_layout->layout();
926 
927     QCOMPARE(m_shape1->textShapeData()->position(), 0);
928     QCOMPARE(m_shape1->textShapeData()->endPosition(), 11);
929     QCOMPARE(shape2->textShapeData()->position(), 12);
930     QCOMPARE(shape2->textShapeData()->endPosition(), 30);
931 }
932 
testShapePosition2()933 void TestDocumentLayout::testShapePosition2()
934 {
935     initForNewTest("Foo\n" + m_loremIpsum);
936 
937     m_shape1->setSize(QSizeF(200, 40));
938     MockTextShape *shape2 = new MockTextShape();
939     shape2->setSize(QSizeF(200, 1000));
940     m_layout->addShape(shape2);
941 
942     m_layout->layout();
943 
944     QCOMPARE(m_shape1->textShapeData()->position(), 0);
945     int split = m_shape1->textShapeData()->endPosition();
946     // qDebug() << split;
947     QVERIFY(split > 4 && split < m_loremIpsum.length());
948     QCOMPARE(shape2->textShapeData()->position(), split + 1);
949     QCOMPARE(shape2->textShapeData()->endPosition(), m_loremIpsum.length() + 5);
950 }
951 
952 QTEST_KDEMAIN(TestDocumentLayout, GUI)
953