source: trunk/src/gui/text/qtextdocumentlayout.cpp@ 219

Last change on this file since 219 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 123.4 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qtextdocumentlayout_p.h"
43#include "qtextdocument_p.h"
44#include "qtextimagehandler_p.h"
45#include "qtexttable.h"
46#include "qtextlist.h"
47#include "qtextengine_p.h"
48#include "private/qcssutil_p.h"
49
50#include "qabstracttextdocumentlayout_p.h"
51#include "qcssparser_p.h"
52
53#include <qpainter.h>
54#include <qmath.h>
55#include <qrect.h>
56#include <qpalette.h>
57#include <qdebug.h>
58#include <qvarlengtharray.h>
59#include <limits.h>
60#include <qstyle.h>
61#include <qbasictimer.h>
62#include "private/qfunctions_p.h"
63
64// #define LAYOUT_DEBUG
65
66#ifdef LAYOUT_DEBUG
67#define LDEBUG qDebug()
68#define INC_INDENT debug_indent += " "
69#define DEC_INDENT debug_indent = debug_indent.left(debug_indent.length()-2)
70#else
71#define LDEBUG if(0) qDebug()
72#define INC_INDENT do {} while(0)
73#define DEC_INDENT do {} while(0)
74#endif
75
76QT_BEGIN_NAMESPACE
77
78extern int qt_defaultDpi();
79
80// ################ should probably add frameFormatChange notification!
81
82struct QLayoutStruct;
83
84class QTextFrameData : public QTextFrameLayoutData
85{
86public:
87 QTextFrameData();
88
89 // relative to parent frame
90 QFixedPoint position;
91 QFixedSize size;
92
93 // contents starts at (margin+border/margin+border)
94 QFixed topMargin;
95 QFixed bottomMargin;
96 QFixed leftMargin;
97 QFixed rightMargin;
98 QFixed border;
99 QFixed padding;
100 // contents width includes padding (as we need to treat this on a per cell basis for tables)
101 QFixed contentsWidth;
102 QFixed contentsHeight;
103 QFixed oldContentsWidth;
104
105 // accumulated margins
106 QFixed effectiveTopMargin;
107 QFixed effectiveBottomMargin;
108
109 QFixed minimumWidth;
110 QFixed maximumWidth;
111
112 QLayoutStruct *currentLayoutStruct;
113
114 bool sizeDirty;
115 bool layoutDirty;
116
117 QList<QPointer<QTextFrame> > floats;
118};
119
120QTextFrameData::QTextFrameData()
121 : maximumWidth(QFIXED_MAX),
122 currentLayoutStruct(0), sizeDirty(true), layoutDirty(true)
123{
124}
125
126struct QLayoutStruct {
127 QLayoutStruct() : maximumWidth(QFIXED_MAX), fullLayout(false)
128 {}
129 QTextFrame *frame;
130 QFixed x_left;
131 QFixed x_right;
132 QFixed frameY; // absolute y position of the current frame
133 QFixed y; // always relative to the current frame
134 QFixed contentsWidth;
135 QFixed minimumWidth;
136 QFixed maximumWidth;
137 bool fullLayout;
138 QList<QTextFrame *> pendingFloats;
139 QFixed pageHeight;
140 QFixed pageBottom;
141 QFixed pageTopMargin;
142 QFixed pageBottomMargin;
143 QRectF updateRect;
144 QRectF updateRectForFloats;
145
146 inline void addUpdateRectForFloat(const QRectF &rect) {
147 if (updateRectForFloats.isValid())
148 updateRectForFloats |= rect;
149 else
150 updateRectForFloats = rect;
151 }
152
153 inline QFixed absoluteY() const
154 { return frameY + y; }
155
156 inline int currentPage() const
157 { return pageHeight == 0 ? 0 : (absoluteY() / pageHeight).truncate(); }
158
159 inline void newPage()
160 { if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY; }
161};
162
163class QTextTableData : public QTextFrameData
164{
165public:
166 QFixed cellSpacing, cellPadding;
167 qreal deviceScale;
168 QVector<QFixed> minWidths;
169 QVector<QFixed> maxWidths;
170 QVector<QFixed> widths;
171 QVector<QFixed> heights;
172 QVector<QFixed> columnPositions;
173 QVector<QFixed> rowPositions;
174
175 QVector<QFixed> cellVerticalOffsets;
176
177 QFixed headerHeight;
178
179 // maps from cell index (row + col * rowCount) to child frames belonging to
180 // the specific cell
181 QMultiHash<int, QTextFrame *> childFrameMap;
182
183 inline QFixed cellWidth(int column, int colspan) const
184 { return columnPositions.at(column + colspan - 1) + widths.at(column + colspan - 1)
185 - columnPositions.at(column); }
186
187 inline void calcRowPosition(int row)
188 {
189 if (row > 0)
190 rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + border + cellSpacing + border;
191 }
192
193 QRectF cellRect(const QTextTableCell &cell) const;
194
195 inline QFixed paddingProperty(const QTextFormat &format, QTextFormat::Property property) const
196 {
197 QVariant v = format.property(property);
198 if (v.isNull()) {
199 return cellPadding;
200 } else {
201 Q_ASSERT(v.type() == QVariant::Double);
202 return QFixed::fromReal(v.toDouble() * deviceScale);
203 }
204 }
205
206 inline QFixed topPadding(const QTextFormat &format) const
207 {
208 return paddingProperty(format, QTextFormat::TableCellTopPadding);
209 }
210
211 inline QFixed bottomPadding(const QTextFormat &format) const
212 {
213 return paddingProperty(format, QTextFormat::TableCellBottomPadding);
214 }
215
216 inline QFixed leftPadding(const QTextFormat &format) const
217 {
218 return paddingProperty(format, QTextFormat::TableCellLeftPadding);
219 }
220
221 inline QFixed rightPadding(const QTextFormat &format) const
222 {
223 return paddingProperty(format, QTextFormat::TableCellRightPadding);
224 }
225
226 inline QFixedPoint cellPosition(const QTextTableCell &cell) const
227 {
228 const QTextFormat fmt = cell.format();
229 return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(fmt), topPadding(fmt));
230 }
231
232 void updateTableSize();
233
234private:
235 inline QFixedPoint cellPosition(int row, int col) const
236 { return QFixedPoint(columnPositions.at(col), rowPositions.at(row) + cellVerticalOffsets.at(col + row * widths.size())); }
237};
238
239static QTextFrameData *createData(QTextFrame *f)
240{
241 QTextFrameData *data;
242 if (qobject_cast<QTextTable *>(f))
243 data = new QTextTableData;
244 else
245 data = new QTextFrameData;
246 f->setLayoutData(data);
247 return data;
248}
249
250static inline QTextFrameData *data(QTextFrame *f)
251{
252 QTextFrameData *data = static_cast<QTextFrameData *>(f->layoutData());
253 if (!data)
254 data = createData(f);
255 return data;
256}
257
258static bool isFrameFromInlineObject(QTextFrame *f)
259{
260 return f->firstPosition() > f->lastPosition();
261}
262
263void QTextTableData::updateTableSize()
264{
265 const QFixed effectiveTopMargin = this->topMargin + border + padding;
266 const QFixed effectiveBottomMargin = this->bottomMargin + border + padding;
267 const QFixed effectiveLeftMargin = this->leftMargin + border + padding;
268 const QFixed effectiveRightMargin = this->rightMargin + border + padding;
269 size.height = contentsHeight == -1
270 ? rowPositions.last() + heights.last() + padding + border + cellSpacing + effectiveBottomMargin
271 : effectiveTopMargin + contentsHeight + effectiveBottomMargin;
272 size.width = effectiveLeftMargin + contentsWidth + effectiveRightMargin;
273}
274
275QRectF QTextTableData::cellRect(const QTextTableCell &cell) const
276{
277 const int row = cell.row();
278 const int rowSpan = cell.rowSpan();
279 const int column = cell.column();
280 const int colSpan = cell.columnSpan();
281
282 return QRectF(columnPositions.at(column).toReal(),
283 rowPositions.at(row).toReal(),
284 (columnPositions.at(column + colSpan - 1) + widths.at(column + colSpan - 1) - columnPositions.at(column)).toReal(),
285 (rowPositions.at(row + rowSpan - 1) + heights.at(row + rowSpan - 1) - rowPositions.at(row)).toReal());
286}
287
288static inline bool isEmptyBlockBeforeTable(const QTextBlock &block, const QTextBlockFormat &format, const QTextFrame::Iterator &nextIt)
289{
290 return !nextIt.atEnd()
291 && qobject_cast<QTextTable *>(nextIt.currentFrame())
292 && block.isValid()
293 && block.length() == 1
294 && !format.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)
295 && !format.hasProperty(QTextFormat::BackgroundBrush)
296 && nextIt.currentFrame()->firstPosition() == block.position() + 1
297 ;
298}
299
300static inline bool isEmptyBlockBeforeTable(QTextFrame::Iterator it)
301{
302 QTextFrame::Iterator next = it; ++next;
303 if (it.currentFrame())
304 return false;
305 QTextBlock block = it.currentBlock();
306 return isEmptyBlockBeforeTable(block, block.blockFormat(), next);
307}
308
309static inline bool isEmptyBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
310{
311 return qobject_cast<const QTextTable *>(previousFrame)
312 && block.isValid()
313 && block.length() == 1
314 && previousFrame->lastPosition() == block.position() - 1
315 ;
316}
317
318static inline bool isLineSeparatorBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
319{
320 return qobject_cast<const QTextTable *>(previousFrame)
321 && block.isValid()
322 && block.length() > 1
323 && block.text().at(0) == QChar::LineSeparator
324 && previousFrame->lastPosition() == block.position() - 1
325 ;
326}
327
328/*
329
330Optimisation strategies:
331
332HTML layout:
333
334* Distinguish between normal and special flow. For normal flow the condition:
335 y1 > y2 holds for all blocks with b1.key() > b2.key().
336* Special flow is: floats, table cells
337
338* Normal flow within table cells. Tables (not cells) are part of the normal flow.
339
340
341* If blocks grows/shrinks in height and extends over whole page width at the end, move following blocks.
342* If height doesn't change, no need to do anything
343
344Table cells:
345
346* If minWidth of cell changes, recalculate table width, relayout if needed.
347* What about maxWidth when doing auto layout?
348
349Floats:
350* need fixed or proportional width, otherwise don't float!
351* On width/height change relayout surrounding paragraphs.
352
353Document width change:
354* full relayout needed
355
356
357Float handling:
358
359* Floats are specified by a special format object.
360* currently only floating images are implemented.
361
362*/
363
364/*
365
366 On the table layouting:
367
368 +---[ table border ]-------------------------
369 | [ cell spacing ]
370 | +------[ cell border ]-----+ +--------
371 | | | |
372 | |
373 | |
374 | |
375 |
376
377 rowPositions[i] and columnPositions[i] point at the cell content
378 position. So for example the left border is drawn at
379 x = columnPositions[i] - fd->border and similar for y.
380
381*/
382
383struct QCheckPoint
384{
385 QFixed y;
386 QFixed frameY; // absolute y position of the current frame
387 int positionInFrame;
388 QFixed minimumWidth;
389 QFixed maximumWidth;
390 QFixed contentsWidth;
391};
392Q_DECLARE_TYPEINFO(QCheckPoint, Q_PRIMITIVE_TYPE);
393
394Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, QFixed y)
395{
396 return checkPoint.y < y;
397}
398
399Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, int pos)
400{
401 return checkPoint.positionInFrame < pos;
402}
403
404static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QPointF &origin, QRectF gradientRect = QRectF())
405{
406 p->save();
407 if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) {
408 if (!gradientRect.isNull()) {
409 QTransform m;
410 m.translate(gradientRect.left(), gradientRect.top());
411 m.scale(gradientRect.width(), gradientRect.height());
412 brush.setTransform(m);
413 const_cast<QGradient *>(brush.gradient())->setCoordinateMode(QGradient::LogicalMode);
414 }
415 } else {
416 p->setBrushOrigin(origin);
417 }
418 p->fillRect(rect, brush);
419 p->restore();
420}
421
422class QTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate
423{
424 Q_DECLARE_PUBLIC(QTextDocumentLayout)
425public:
426 QTextDocumentLayoutPrivate();
427
428 QTextOption::WrapMode wordWrapMode;
429#ifdef LAYOUT_DEBUG
430 mutable QString debug_indent;
431#endif
432
433 int fixedColumnWidth;
434 int cursorWidth;
435
436 QSizeF lastReportedSize;
437 QRectF viewportRect;
438 QRectF clipRect;
439
440 mutable int currentLazyLayoutPosition;
441 mutable int lazyLayoutStepSize;
442 QBasicTimer layoutTimer;
443 mutable QBasicTimer sizeChangedTimer;
444 uint showLayoutProgress : 1;
445 uint insideDocumentChange : 1;
446
447 int lastPageCount;
448 qreal idealWidth;
449 bool contentHasAlignment;
450
451 QFixed blockIndent(const QTextBlockFormat &blockFormat) const;
452
453 void drawFrame(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
454 QTextFrame *f) const;
455 void drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
456 QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const;
457 void drawBlock(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
458 QTextBlock bl, bool inRootFrame) const;
459 void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
460 QTextBlock bl, const QTextCharFormat *selectionFormat) const;
461 void drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
462 QTextTable *table, QTextTableData *td, int r, int c,
463 QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const;
464 void drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, qreal border,
465 const QBrush &brush, QTextFrameFormat::BorderStyle style) const;
466 void drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const;
467
468 enum HitPoint {
469 PointBefore,
470 PointAfter,
471 PointInside,
472 PointExact
473 };
474 HitPoint hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
475 HitPoint hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
476 int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
477 HitPoint hitTest(QTextTable *table, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
478 HitPoint hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const;
479
480 QLayoutStruct layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
481 int layoutFrom, int layoutTo, QTextTableData *tableData, QFixed absoluteTableY,
482 bool withPageBreaks);
483 void setCellPosition(QTextTable *t, const QTextTableCell &cell, const QPointF &pos);
484 QRectF layoutTable(QTextTable *t, int layoutFrom, int layoutTo, QFixed parentY);
485
486 void positionFloat(QTextFrame *frame, QTextLine *currentLine = 0);
487
488 // calls the next one
489 QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY = 0);
490 QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY = 0);
491
492 void layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
493 QLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat);
494 void layoutFlow(QTextFrame::Iterator it, QLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0);
495 void pageBreakInsideTable(QTextTable *table, QLayoutStruct *layoutStruct);
496
497
498 void floatMargins(const QFixed &y, const QLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const;
499 QFixed findY(QFixed yFrom, const QLayoutStruct *layoutStruct, QFixed requiredWidth) const;
500
501 QVector<QCheckPoint> checkPoints;
502
503 QTextFrame::Iterator frameIteratorForYPosition(QFixed y) const;
504 QTextFrame::Iterator frameIteratorForTextPosition(int position) const;
505
506 void ensureLayouted(QFixed y) const;
507 void ensureLayoutedByPosition(int position) const;
508 inline void ensureLayoutFinished() const
509 { ensureLayoutedByPosition(INT_MAX); }
510 void layoutStep() const;
511
512 QRectF frameBoundingRectInternal(QTextFrame *frame) const;
513
514 qreal scaleToDevice(qreal value) const;
515 QFixed scaleToDevice(QFixed value) const;
516};
517
518QTextDocumentLayoutPrivate::QTextDocumentLayoutPrivate()
519 : fixedColumnWidth(-1),
520 cursorWidth(1),
521 currentLazyLayoutPosition(-1),
522 lazyLayoutStepSize(1000),
523 lastPageCount(-1)
524{
525 showLayoutProgress = true;
526 insideDocumentChange = false;
527 idealWidth = 0;
528 contentHasAlignment = false;
529}
530
531QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForYPosition(QFixed y) const
532{
533 QTextFrame *rootFrame = document->rootFrame();
534
535 if (checkPoints.isEmpty()
536 || y < 0 || y > data(rootFrame)->size.height)
537 return rootFrame->begin();
538
539 QVector<QCheckPoint>::ConstIterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), y);
540 if (checkPoint == checkPoints.end())
541 return rootFrame->begin();
542
543 if (checkPoint != checkPoints.begin())
544 --checkPoint;
545
546 const int position = rootFrame->firstPosition() + checkPoint->positionInFrame;
547 return frameIteratorForTextPosition(position);
548}
549
550QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForTextPosition(int position) const
551{
552 QTextFrame *rootFrame = docPrivate->rootFrame();
553
554 const QTextDocumentPrivate::BlockMap &map = docPrivate->blockMap();
555 const int begin = map.findNode(rootFrame->firstPosition());
556 const int end = map.findNode(rootFrame->lastPosition()+1);
557
558 const int block = map.findNode(position);
559 const int blockPos = map.position(block);
560
561 QTextFrame::iterator it(rootFrame, block, begin, end);
562
563 QTextFrame *containingFrame = docPrivate->frameAt(blockPos);
564 if (containingFrame != rootFrame) {
565 while (containingFrame->parentFrame() != rootFrame) {
566 containingFrame = containingFrame->parentFrame();
567 Q_ASSERT(containingFrame);
568 }
569
570 it.cf = containingFrame;
571 it.cb = 0;
572 }
573
574 return it;
575}
576
577QTextDocumentLayoutPrivate::HitPoint
578QTextDocumentLayoutPrivate::hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
579{
580 QTextFrameData *fd = data(frame);
581 // #########
582 if (fd->layoutDirty)
583 return PointAfter;
584 Q_ASSERT(!fd->layoutDirty);
585 Q_ASSERT(!fd->sizeDirty);
586 const QFixedPoint relativePoint(point.x - fd->position.x, point.y - fd->position.y);
587
588 QTextFrame *rootFrame = docPrivate->rootFrame();
589
590// LDEBUG << "checking frame" << frame->firstPosition() << "point=" << point
591// << "position" << fd->position << "size" << fd->size;
592 if (frame != rootFrame) {
593 if (relativePoint.y < 0 || relativePoint.x < 0) {
594 *position = frame->firstPosition() - 1;
595// LDEBUG << "before pos=" << *position;
596 return PointBefore;
597 } else if (relativePoint.y > fd->size.height || relativePoint.x > fd->size.width) {
598 *position = frame->lastPosition() + 1;
599// LDEBUG << "after pos=" << *position;
600 return PointAfter;
601 }
602 }
603
604 if (isFrameFromInlineObject(frame)) {
605 *position = frame->firstPosition() - 1;
606 return PointExact;
607 }
608
609 if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
610 const int rows = table->rows();
611 const int columns = table->columns();
612 QTextTableData *td = static_cast<QTextTableData *>(data(table));
613
614 if (!td->childFrameMap.isEmpty()) {
615 for (int r = 0; r < rows; ++r) {
616 for (int c = 0; c < columns; ++c) {
617 QTextTableCell cell = table->cellAt(r, c);
618 if (cell.row() != r || cell.column() != c)
619 continue;
620
621 QRectF cellRect = td->cellRect(cell);
622 const QFixedPoint cellPos = QFixedPoint::fromPointF(cellRect.topLeft());
623 const QFixedPoint pointInCell = relativePoint - cellPos;
624
625 const QList<QTextFrame *> childFrames = td->childFrameMap.values(r + c * rows);
626 for (int i = 0; i < childFrames.size(); ++i) {
627 QTextFrame *child = childFrames.at(i);
628 if (isFrameFromInlineObject(child)
629 && child->frameFormat().position() != QTextFrameFormat::InFlow
630 && hitTest(child, pointInCell, position, l, accuracy) == PointExact)
631 {
632 return PointExact;
633 }
634 }
635 }
636 }
637 }
638
639 return hitTest(table, relativePoint, position, l, accuracy);
640 }
641
642 const QList<QTextFrame *> childFrames = frame->childFrames();
643 for (int i = 0; i < childFrames.size(); ++i) {
644 QTextFrame *child = childFrames.at(i);
645 if (isFrameFromInlineObject(child)
646 && child->frameFormat().position() != QTextFrameFormat::InFlow
647 && hitTest(child, relativePoint, position, l, accuracy) == PointExact)
648 {
649 return PointExact;
650 }
651 }
652
653 QTextFrame::Iterator it = frame->begin();
654
655 if (frame == rootFrame) {
656 it = frameIteratorForYPosition(relativePoint.y);
657
658 Q_ASSERT(it.parentFrame() == frame);
659 }
660
661 if (it.currentFrame())
662 *position = it.currentFrame()->firstPosition();
663 else
664 *position = it.currentBlock().position();
665
666 return hitTest(it, PointBefore, relativePoint, position, l, accuracy);
667}
668
669QTextDocumentLayoutPrivate::HitPoint
670QTextDocumentLayoutPrivate::hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p,
671 int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
672{
673 INC_INDENT;
674
675 for (; !it.atEnd(); ++it) {
676 QTextFrame *c = it.currentFrame();
677 HitPoint hp;
678 int pos = -1;
679 if (c) {
680 hp = hitTest(c, p, &pos, l, accuracy);
681 } else {
682 hp = hitTest(it.currentBlock(), p, &pos, l, accuracy);
683 }
684 if (hp >= PointInside) {
685 if (isEmptyBlockBeforeTable(it))
686 continue;
687 hit = hp;
688 *position = pos;
689 break;
690 }
691 if (hp == PointBefore && pos < *position) {
692 *position = pos;
693 hit = hp;
694 } else if (hp == PointAfter && pos > *position) {
695 *position = pos;
696 hit = hp;
697 }
698 }
699
700 DEC_INDENT;
701// LDEBUG << "inside=" << hit << " pos=" << *position;
702 return hit;
703}
704
705QTextDocumentLayoutPrivate::HitPoint
706QTextDocumentLayoutPrivate::hitTest(QTextTable *table, const QFixedPoint &point,
707 int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const
708{
709 QTextTableData *td = static_cast<QTextTableData *>(data(table));
710
711 QVector<QFixed>::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), point.y);
712 if (rowIt == td->rowPositions.constEnd()) {
713 rowIt = td->rowPositions.constEnd() - 1;
714 } else if (rowIt != td->rowPositions.constBegin()) {
715 --rowIt;
716 }
717
718 QVector<QFixed>::ConstIterator colIt = qLowerBound(td->columnPositions.constBegin(), td->columnPositions.constEnd(), point.x);
719 if (colIt == td->columnPositions.constEnd()) {
720 colIt = td->columnPositions.constEnd() - 1;
721 } else if (colIt != td->columnPositions.constBegin()) {
722 --colIt;
723 }
724
725 QTextTableCell cell = table->cellAt(rowIt - td->rowPositions.constBegin(),
726 colIt - td->columnPositions.constBegin());
727 if (!cell.isValid())
728 return PointBefore;
729
730 *position = cell.firstPosition();
731
732 HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(cell), position, l, accuracy);
733
734 if (hp == PointExact)
735 return hp;
736 if (hp == PointAfter)
737 *position = cell.lastPosition();
738 return PointInside;
739}
740
741QTextDocumentLayoutPrivate::HitPoint
742QTextDocumentLayoutPrivate::hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l,
743 Qt::HitTestAccuracy accuracy) const
744{
745 QTextLayout *tl = bl.layout();
746 QRectF textrect = tl->boundingRect();
747 textrect.translate(tl->position());
748// LDEBUG << " checking block" << bl.position() << "point=" << point
749// << " tlrect" << textrect;
750 *position = bl.position();
751 if (point.y.toReal() < textrect.top()) {
752// LDEBUG << " before pos=" << *position;
753 return PointBefore;
754 } else if (point.y.toReal() > textrect.bottom()) {
755 *position += bl.length();
756// LDEBUG << " after pos=" << *position;
757 return PointAfter;
758 }
759
760 QPointF pos = point.toPointF() - tl->position();
761
762 // ### rtl?
763
764 HitPoint hit = PointInside;
765 *l = tl;
766 int off = 0;
767 for (int i = 0; i < tl->lineCount(); ++i) {
768 QTextLine line = tl->lineAt(i);
769 const QRectF lr = line.naturalTextRect();
770 if (lr.top() > pos.y()) {
771 off = qMin(off, line.textStart());
772 } else if (lr.bottom() <= pos.y()) {
773 off = qMax(off, line.textStart() + line.textLength());
774 } else {
775 if (lr.left() <= pos.x() && lr.right() >= pos.x())
776 hit = PointExact;
777 // when trying to hit an anchor we want it to hit not only in the left
778 // half
779 if (accuracy == Qt::ExactHit)
780 off = line.xToCursor(pos.x(), QTextLine::CursorOnCharacter);
781 else
782 off = line.xToCursor(pos.x(), QTextLine::CursorBetweenCharacters);
783 break;
784 }
785 }
786 *position += off;
787
788// LDEBUG << " inside=" << hit << " pos=" << *position;
789 return hit;
790}
791
792// ### could be moved to QTextBlock
793QFixed QTextDocumentLayoutPrivate::blockIndent(const QTextBlockFormat &blockFormat) const
794{
795 qreal indent = blockFormat.indent();
796
797 QTextObject *object = document->objectForFormat(blockFormat);
798 if (object)
799 indent += object->format().toListFormat().indent();
800
801 if (qIsNull(indent))
802 return 0;
803
804 qreal scale = 1;
805 if (paintDevice) {
806 scale = qreal(paintDevice->logicalDpiY()) / qreal(qt_defaultDpi());
807 }
808
809 return QFixed::fromReal(indent * scale * document->indentWidth());
810}
811
812void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin,
813 qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const
814{
815 const qreal pageHeight = document->pageSize().height();
816 const int topPage = pageHeight > 0 ? static_cast<int>(rect.top() / pageHeight) : 0;
817 const int bottomPage = pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0;
818
819#ifndef QT_NO_CSSPARSER
820 QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(style + 1);
821#endif //QT_NO_CSSPARSER
822
823 bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing);
824 painter->setRenderHint(QPainter::Antialiasing);
825
826 for (int i = topPage; i <= bottomPage; ++i) {
827 QRectF clipped = rect.toRect();
828
829 if (topPage != bottomPage) {
830 clipped.setTop(qMax(clipped.top(), i * pageHeight + topMargin - border));
831 clipped.setBottom(qMin(clipped.bottom(), (i + 1) * pageHeight - bottomMargin));
832
833 if (clipped.bottom() <= clipped.top())
834 continue;
835 }
836#ifndef QT_NO_CSSPARSER
837 qDrawEdge(painter, clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border, 0, 0, QCss::LeftEdge, cssStyle, brush);
838 qDrawEdge(painter, clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border, 0, 0, QCss::TopEdge, cssStyle, brush);
839 qDrawEdge(painter, clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom(), 0, 0, QCss::RightEdge, cssStyle, brush);
840 qDrawEdge(painter, clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border, 0, 0, QCss::BottomEdge, cssStyle, brush);
841#else
842 painter->save();
843 painter->setPen(Qt::NoPen);
844 painter->setBrush(brush);
845 painter->drawRect(QRectF(clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border));
846 painter->drawRect(QRectF(clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border));
847 painter->drawRect(QRectF(clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom()));
848 painter->drawRect(QRectF(clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border));
849 painter->restore();
850#endif //QT_NO_CSSPARSER
851 }
852 if (turn_off_antialiasing)
853 painter->setRenderHint(QPainter::Antialiasing, false);
854}
855
856void QTextDocumentLayoutPrivate::drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const
857{
858 if (fd->border != 0) {
859 painter->save();
860 painter->setBrush(Qt::lightGray);
861 painter->setPen(Qt::NoPen);
862
863 const qreal leftEdge = rect.left() + fd->leftMargin.toReal();
864 const qreal border = fd->border.toReal();
865 const qreal topMargin = fd->topMargin.toReal();
866 const qreal leftMargin = fd->leftMargin.toReal();
867 const qreal bottomMargin = fd->bottomMargin.toReal();
868 const qreal rightMargin = fd->rightMargin.toReal();
869 const qreal w = rect.width() - 2 * border - leftMargin - rightMargin;
870 const qreal h = rect.height() - 2 * border - topMargin - bottomMargin;
871
872 drawBorder(painter, QRectF(leftEdge, rect.top() + topMargin, w + border, h + border),
873 fd->effectiveTopMargin.toReal(), fd->effectiveBottomMargin.toReal(),
874 border, frame->frameFormat().borderBrush(), frame->frameFormat().borderStyle());
875
876 painter->restore();
877 }
878
879 const QBrush bg = frame->frameFormat().background();
880 if (bg != Qt::NoBrush) {
881 QRectF bgRect = rect;
882 bgRect.adjust((fd->leftMargin + fd->border).toReal(),
883 (fd->topMargin + fd->border).toReal(),
884 - (fd->rightMargin + fd->border).toReal(),
885 - (fd->bottomMargin + fd->border).toReal());
886
887 QRectF gradientRect; // invalid makes it default to bgRect
888 QPointF origin = bgRect.topLeft();
889 if (!frame->parentFrame()) {
890 bgRect = clip;
891 gradientRect.setWidth(painter->device()->width());
892 gradientRect.setHeight(painter->device()->height());
893 }
894 fillBackground(painter, bgRect, bg, origin, gradientRect);
895 }
896}
897
898static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintContext &cell_context,
899 const QTextTableCell &cell,
900 int r, int c,
901 const int *selectedTableCells)
902{
903 for (int i = 0; i < cell_context.selections.size(); ++i) {
904 int row_start = selectedTableCells[i * 4];
905 int col_start = selectedTableCells[i * 4 + 1];
906 int num_rows = selectedTableCells[i * 4 + 2];
907 int num_cols = selectedTableCells[i * 4 + 3];
908
909 if (row_start != -1) {
910 if (r >= row_start && r < row_start + num_rows
911 && c >= col_start && c < col_start + num_cols)
912 {
913 int firstPosition = cell.firstPosition();
914 int lastPosition = cell.lastPosition();
915
916 // make sure empty cells are still selected
917 if (firstPosition == lastPosition)
918 ++lastPosition;
919
920 cell_context.selections[i].cursor.setPosition(firstPosition);
921 cell_context.selections[i].cursor.setPosition(lastPosition, QTextCursor::KeepAnchor);
922 } else {
923 cell_context.selections[i].cursor.clearSelection();
924 }
925 }
926
927 // FullWidthSelection is not useful for tables
928 cell_context.selections[i].format.clearProperty(QTextFormat::FullWidthSelection);
929 }
930}
931
932void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *painter,
933 const QAbstractTextDocumentLayout::PaintContext &context,
934 QTextFrame *frame) const
935{
936 QTextFrameData *fd = data(frame);
937 // #######
938 if (fd->layoutDirty)
939 return;
940 Q_ASSERT(!fd->sizeDirty);
941 Q_ASSERT(!fd->layoutDirty);
942
943 const QPointF off = offset + fd->position.toPointF();
944 if (context.clip.isValid()
945 && (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top()
946 || off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left()))
947 return;
948
949// LDEBUG << debug_indent << "drawFrame" << frame->firstPosition() << "--" << frame->lastPosition() << "at" << offset;
950// INC_INDENT;
951
952 // if the cursor is /on/ a table border we may need to repaint it
953 // afterwards, as we usually draw the decoration first
954 QTextBlock cursorBlockNeedingRepaint;
955 QPointF offsetOfRepaintedCursorBlock = off;
956
957 QTextTable *table = qobject_cast<QTextTable *>(frame);
958 const QRectF frameRect(off, fd->size.toSizeF());
959
960 if (table) {
961 const int rows = table->rows();
962 const int columns = table->columns();
963 QTextTableData *td = static_cast<QTextTableData *>(data(table));
964
965 QVarLengthArray<int> selectedTableCells(context.selections.size() * 4);
966 for (int i = 0; i < context.selections.size(); ++i) {
967 const QAbstractTextDocumentLayout::Selection &s = context.selections.at(i);
968 int row_start = -1, col_start = -1, num_rows = -1, num_cols = -1;
969
970 if (s.cursor.currentTable() == table)
971 s.cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
972
973 selectedTableCells[i * 4] = row_start;
974 selectedTableCells[i * 4 + 1] = col_start;
975 selectedTableCells[i * 4 + 2] = num_rows;
976 selectedTableCells[i * 4 + 3] = num_cols;
977 }
978
979 QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
980 if (pageHeight <= 0)
981 pageHeight = QFIXED_MAX;
982
983 const int tableStartPage = (td->position.y / pageHeight).truncate();
984 const int tableEndPage = ((td->position.y + td->size.height) / pageHeight).truncate();
985
986 qreal border = td->border.toReal();
987 drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
988
989 // draw the table headers
990 const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
991 int page = tableStartPage + 1;
992 while (page <= tableEndPage) {
993 const QFixed pageTop = page * pageHeight + td->effectiveTopMargin + td->cellSpacing + td->border;
994 const qreal headerOffset = (pageTop - td->rowPositions.at(0)).toReal();
995 for (int r = 0; r < headerRowCount; ++r) {
996 for (int c = 0; c < columns; ++c) {
997 QTextTableCell cell = table->cellAt(r, c);
998 QAbstractTextDocumentLayout::PaintContext cell_context = context;
999 adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
1000 QRectF cellRect = td->cellRect(cell);
1001
1002 cellRect.translate(off.x(), headerOffset);
1003 // we need to account for the cell border in the clipping test
1004 int leftAdjust = qMin(qreal(0), 1 - border);
1005 if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
1006 continue;
1007
1008 drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1009 &offsetOfRepaintedCursorBlock);
1010 }
1011 }
1012 ++page;
1013 }
1014
1015 int firstRow = 0;
1016 int lastRow = rows;
1017
1018 if (context.clip.isValid()) {
1019 QVector<QFixed>::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.top() - off.y()));
1020 if (rowIt != td->rowPositions.constEnd() && rowIt != td->rowPositions.constBegin()) {
1021 --rowIt;
1022 firstRow = rowIt - td->rowPositions.constBegin();
1023 }
1024
1025 rowIt = qUpperBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.bottom() - off.y()));
1026 if (rowIt != td->rowPositions.constEnd()) {
1027 ++rowIt;
1028 lastRow = rowIt - td->rowPositions.constBegin();
1029 }
1030 }
1031
1032 for (int c = 0; c < columns; ++c) {
1033 QTextTableCell cell = table->cellAt(firstRow, c);
1034 firstRow = qMin(firstRow, cell.row());
1035 }
1036
1037 for (int r = firstRow; r < lastRow; ++r) {
1038 for (int c = 0; c < columns; ++c) {
1039 QTextTableCell cell = table->cellAt(r, c);
1040 QAbstractTextDocumentLayout::PaintContext cell_context = context;
1041 adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data());
1042 QRectF cellRect = td->cellRect(cell);
1043
1044 cellRect.translate(off);
1045 // we need to account for the cell border in the clipping test
1046 int leftAdjust = qMin(qreal(0), 1 - border);
1047 if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip))
1048 continue;
1049
1050 drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint,
1051 &offsetOfRepaintedCursorBlock);
1052 }
1053 }
1054
1055 } else {
1056 drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
1057
1058 QTextFrame::Iterator it = frame->begin();
1059
1060 if (frame == docPrivate->rootFrame())
1061 it = frameIteratorForYPosition(QFixed::fromReal(context.clip.top()));
1062
1063 QList<QTextFrame *> floats;
1064 for (int i = 0; i < fd->floats.count(); ++i)
1065 floats.append(fd->floats.at(i));
1066
1067 drawFlow(off, painter, context, it, floats, &cursorBlockNeedingRepaint);
1068 }
1069
1070 if (cursorBlockNeedingRepaint.isValid()) {
1071 const QPen oldPen = painter->pen();
1072 painter->setPen(context.palette.color(QPalette::Text));
1073 const int cursorPos = context.cursorPosition - cursorBlockNeedingRepaint.position();
1074 cursorBlockNeedingRepaint.layout()->drawCursor(painter, offsetOfRepaintedCursorBlock,
1075 cursorPos, cursorWidth);
1076 painter->setPen(oldPen);
1077 }
1078
1079// DEC_INDENT;
1080
1081 return;
1082}
1083
1084void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context,
1085 QTextTable *table, QTextTableData *td, int r, int c,
1086 QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const
1087{
1088 QTextTableCell cell = table->cellAt(r, c);
1089 int rspan = cell.rowSpan();
1090 int cspan = cell.columnSpan();
1091 if (rspan != 1) {
1092 int cr = cell.row();
1093 if (cr != r)
1094 return;
1095 }
1096 if (cspan != 1) {
1097 int cc = cell.column();
1098 if (cc != c)
1099 return;
1100 }
1101
1102 QTextFormat fmt = cell.format();
1103 const QFixed leftPadding = td->leftPadding(fmt);
1104 const QFixed topPadding = td->topPadding(fmt);
1105
1106 if (td->border != 0) {
1107 const QBrush oldBrush = painter->brush();
1108 const QPen oldPen = painter->pen();
1109
1110 const qreal border = td->border.toReal();
1111
1112 QRectF borderRect(cellRect.left() - border, cellRect.top() - border, cellRect.width() + border, cellRect.height() + border);
1113
1114 // invert the border style for cells
1115 QTextFrameFormat::BorderStyle cellBorder = table->format().borderStyle();
1116 switch (cellBorder) {
1117 case QTextFrameFormat::BorderStyle_Inset:
1118 cellBorder = QTextFrameFormat::BorderStyle_Outset;
1119 break;
1120 case QTextFrameFormat::BorderStyle_Outset:
1121 cellBorder = QTextFrameFormat::BorderStyle_Inset;
1122 break;
1123 case QTextFrameFormat::BorderStyle_Groove:
1124 cellBorder = QTextFrameFormat::BorderStyle_Ridge;
1125 break;
1126 case QTextFrameFormat::BorderStyle_Ridge:
1127 cellBorder = QTextFrameFormat::BorderStyle_Groove;
1128 break;
1129 default:
1130 break;
1131 }
1132
1133 qreal topMargin = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal();
1134 qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal();
1135
1136 const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1);
1137 if (r >= headerRowCount)
1138 topMargin += td->headerHeight.toReal();
1139
1140 drawBorder(painter, borderRect, topMargin, bottomMargin,
1141 border, table->format().borderBrush(), cellBorder);
1142
1143 painter->setBrush(oldBrush);
1144 painter->setPen(oldPen);
1145 }
1146
1147 const QBrush bg = cell.format().background();
1148 const QPointF brushOrigin = painter->brushOrigin();
1149 if (bg.style() != Qt::NoBrush) {
1150 fillBackground(painter, cellRect, bg, cellRect.topLeft());
1151
1152 if (bg.style() > Qt::SolidPattern)
1153 painter->setBrushOrigin(cellRect.topLeft());
1154 }
1155
1156 const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns());
1157
1158 const QPointF cellPos = QPointF(cellRect.left() + leftPadding.toReal(),
1159 cellRect.top() + (topPadding + verticalOffset).toReal());
1160
1161 QTextBlock repaintBlock;
1162 drawFlow(cellPos, painter, cell_context, cell.begin(),
1163 td->childFrameMap.values(r + c * table->rows()),
1164 &repaintBlock);
1165 if (repaintBlock.isValid()) {
1166 *cursorBlockNeedingRepaint = repaintBlock;
1167 *cursorBlockOffset = cellPos;
1168 }
1169
1170 if (bg.style() > Qt::SolidPattern)
1171 painter->setBrushOrigin(brushOrigin);
1172}
1173
1174void QTextDocumentLayoutPrivate::drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context,
1175 QTextFrame::Iterator it, const QList<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const
1176{
1177 Q_Q(const QTextDocumentLayout);
1178 const bool inRootFrame = (!it.atEnd() && it.parentFrame() && it.parentFrame()->parentFrame() == 0);
1179
1180 QVector<QCheckPoint>::ConstIterator lastVisibleCheckPoint = checkPoints.end();
1181 if (inRootFrame && context.clip.isValid()) {
1182 lastVisibleCheckPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), QFixed::fromReal(context.clip.bottom()));
1183 }
1184
1185 QTextBlock previousBlock;
1186 QTextFrame *previousFrame = 0;
1187
1188 for (; !it.atEnd(); ++it) {
1189 QTextFrame *c = it.currentFrame();
1190
1191 if (inRootFrame && !checkPoints.isEmpty()) {
1192 int currentPosInDoc;
1193 if (c)
1194 currentPosInDoc = c->firstPosition();
1195 else
1196 currentPosInDoc = it.currentBlock().position();
1197
1198 // if we're past what is already laid out then we're better off
1199 // not trying to draw things that may not be positioned correctly yet
1200 if (currentPosInDoc >= checkPoints.last().positionInFrame)
1201 break;
1202
1203 if (lastVisibleCheckPoint != checkPoints.end()
1204 && context.clip.isValid()
1205 && currentPosInDoc >= lastVisibleCheckPoint->positionInFrame
1206 )
1207 break;
1208 }
1209
1210 if (c)
1211 drawFrame(offset, painter, context, c);
1212 else {
1213 QAbstractTextDocumentLayout::PaintContext pc = context;
1214 if (isEmptyBlockAfterTable(it.currentBlock(), previousFrame))
1215 pc.selections.clear();
1216 drawBlock(offset, painter, pc, it.currentBlock(), inRootFrame);
1217 }
1218
1219 // when entering a table and the previous block is empty
1220 // then layoutFlow 'hides' the block that just causes a
1221 // new line by positioning it /on/ the table border. as we
1222 // draw that block before the table itself the decoration
1223 // 'overpaints' the cursor and we need to paint it afterwards
1224 // again
1225 if (isEmptyBlockBeforeTable(previousBlock, previousBlock.blockFormat(), it)
1226 && previousBlock.contains(context.cursorPosition)
1227 ) {
1228 *cursorBlockNeedingRepaint = previousBlock;
1229 }
1230
1231 previousBlock = it.currentBlock();
1232 previousFrame = c;
1233 }
1234
1235 for (int i = 0; i < floats.count(); ++i) {
1236 QTextFrame *frame = floats.at(i);
1237 if (!isFrameFromInlineObject(frame)
1238 || frame->frameFormat().position() == QTextFrameFormat::InFlow)
1239 continue;
1240
1241 const int pos = frame->firstPosition() - 1;
1242 QTextCharFormat format = const_cast<QTextDocumentLayout *>(q)->format(pos);
1243 QTextObjectInterface *handler = q->handlerForObject(format.objectType());
1244 if (handler) {
1245 QRectF rect = frameBoundingRectInternal(frame);
1246 handler->drawObject(painter, rect, document, pos, format);
1247 }
1248 }
1249}
1250
1251void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *painter,
1252 const QAbstractTextDocumentLayout::PaintContext &context,
1253 QTextBlock bl, bool inRootFrame) const
1254{
1255 const QTextLayout *tl = bl.layout();
1256 QRectF r = tl->boundingRect();
1257 r.translate(offset + tl->position());
1258 if (context.clip.isValid() && (r.bottom() < context.clip.y() || r.top() > context.clip.bottom()))
1259 return;
1260// LDEBUG << debug_indent << "drawBlock" << bl.position() << "at" << offset << "br" << tl->boundingRect();
1261
1262 QTextBlockFormat blockFormat = bl.blockFormat();
1263
1264 QBrush bg = blockFormat.background();
1265 if (bg != Qt::NoBrush) {
1266 QRectF rect = r;
1267
1268 // extend the background rectangle if we're in the root frame with NoWrap,
1269 // as the rect of the text block will then be only the width of the text
1270 // instead of the full page width
1271 if (inRootFrame && document->pageSize().width() <= 0) {
1272 const QTextFrameData *fd = data(document->rootFrame());
1273 rect.setRight((fd->size.width - fd->rightMargin).toReal());
1274 }
1275
1276 fillBackground(painter, rect, bg, r.topLeft());
1277 }
1278
1279 QVector<QTextLayout::FormatRange> selections;
1280 int blpos = bl.position();
1281 int bllen = bl.length();
1282 const QTextCharFormat *selFormat = 0;
1283 for (int i = 0; i < context.selections.size(); ++i) {
1284 const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
1285 const int selStart = range.cursor.selectionStart() - blpos;
1286 const int selEnd = range.cursor.selectionEnd() - blpos;
1287 if (selStart < bllen && selEnd > 0
1288 && selEnd > selStart) {
1289 QTextLayout::FormatRange o;
1290 o.start = selStart;
1291 o.length = selEnd - selStart;
1292 o.format = range.format;
1293 selections.append(o);
1294 } else if (! range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection)
1295 && bl.contains(range.cursor.position())) {
1296 // for full width selections we don't require an actual selection, just
1297 // a position to specify the line. that's more convenience in usage.
1298 QTextLayout::FormatRange o;
1299 QTextLine l = tl->lineForTextPosition(range.cursor.position() - blpos);
1300 o.start = l.textStart();
1301 o.length = l.textLength();
1302 if (o.start + o.length == bllen - 1)
1303 ++o.length; // include newline
1304 o.format = range.format;
1305 selections.append(o);
1306 }
1307 if (selStart < 0 && selEnd >= 1)
1308 selFormat = &range.format;
1309 }
1310
1311 QTextObject *object = document->objectForFormat(bl.blockFormat());
1312 if (object && object->format().toListFormat().style() != QTextListFormat::ListStyleUndefined)
1313 drawListItem(offset, painter, context, bl, selFormat);
1314
1315 QPen oldPen = painter->pen();
1316 painter->setPen(context.palette.color(QPalette::Text));
1317
1318 tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect);
1319
1320 if ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen)
1321 || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty())) {
1322 int cpos = context.cursorPosition;
1323 if (cpos < -1)
1324 cpos = tl->preeditAreaPosition() - (cpos + 2);
1325 else
1326 cpos -= blpos;
1327 tl->drawCursor(painter, offset, cpos, cursorWidth);
1328 }
1329
1330 if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
1331 const qreal width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth).value(r.width());
1332 painter->setPen(context.palette.color(QPalette::Dark));
1333 qreal y = r.bottom();
1334 if (bl.length() == 1)
1335 y = r.top() + r.height() / 2;
1336
1337 const qreal middleX = r.left() + r.width() / 2;
1338 painter->drawLine(QLineF(middleX - width / 2, y, middleX + width / 2, y));
1339 }
1340
1341 painter->setPen(oldPen);
1342}
1343
1344
1345void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *painter,
1346 const QAbstractTextDocumentLayout::PaintContext &context,
1347 QTextBlock bl, const QTextCharFormat *selectionFormat) const
1348{
1349 Q_Q(const QTextDocumentLayout);
1350 const QTextBlockFormat blockFormat = bl.blockFormat();
1351 const QTextCharFormat charFormat = QTextCursor(bl).charFormat();
1352 QFont font(charFormat.font());
1353 if (q->paintDevice())
1354 font = QFont(font, q->paintDevice());
1355
1356 const QFontMetrics fontMetrics(font);
1357 QTextObject * const object = document->objectForFormat(blockFormat);
1358 const QTextListFormat lf = object->format().toListFormat();
1359 int style = lf.style();
1360 QString itemText;
1361 QSizeF size;
1362
1363 if (blockFormat.hasProperty(QTextFormat::ListStyle))
1364 style = QTextListFormat::Style(blockFormat.intProperty(QTextFormat::ListStyle));
1365
1366 QTextLayout *layout = bl.layout();
1367 if (layout->lineCount() == 0)
1368 return;
1369 QTextLine firstLine = layout->lineAt(0);
1370 Q_ASSERT(firstLine.isValid());
1371 QPointF pos = (offset + layout->position()).toPoint();
1372 Qt::LayoutDirection dir = docPrivate->defaultTextOption.textDirection();
1373 if (blockFormat.hasProperty(QTextFormat::LayoutDirection))
1374 dir = blockFormat.layoutDirection();
1375 {
1376 QRectF textRect = firstLine.naturalTextRect();
1377 pos += textRect.topLeft().toPoint();
1378 if (dir == Qt::RightToLeft)
1379 pos.rx() += textRect.width();
1380 }
1381
1382 switch (style) {
1383 case QTextListFormat::ListDecimal:
1384 case QTextListFormat::ListLowerAlpha:
1385 case QTextListFormat::ListUpperAlpha:
1386 itemText = static_cast<QTextList *>(object)->itemText(bl);
1387 size.setWidth(fontMetrics.width(itemText));
1388 size.setHeight(fontMetrics.height());
1389 break;
1390
1391 case QTextListFormat::ListSquare:
1392 case QTextListFormat::ListCircle:
1393 case QTextListFormat::ListDisc:
1394 size.setWidth(fontMetrics.lineSpacing() / 3);
1395 size.setHeight(size.width());
1396 break;
1397
1398 case QTextListFormat::ListStyleUndefined:
1399 return;
1400 default: return;
1401 }
1402
1403 QRectF r(pos, size);
1404
1405 qreal xoff = fontMetrics.width(QLatin1Char(' '));
1406 if (dir == Qt::LeftToRight)
1407 xoff = -xoff - size.width();
1408 r.translate( xoff, (fontMetrics.height() / 2 - size.height() / 2));
1409
1410 painter->save();
1411
1412 painter->setRenderHint(QPainter::Antialiasing);
1413
1414 if (selectionFormat) {
1415 painter->setPen(QPen(selectionFormat->foreground(), 0));
1416 painter->fillRect(r, selectionFormat->background());
1417 } else {
1418 QBrush fg = charFormat.foreground();
1419 if (fg == Qt::NoBrush)
1420 fg = context.palette.text();
1421 painter->setPen(QPen(fg, 0));
1422 }
1423
1424 QBrush brush = context.palette.brush(QPalette::Text);
1425
1426 switch (style) {
1427 case QTextListFormat::ListDecimal:
1428 case QTextListFormat::ListLowerAlpha:
1429 case QTextListFormat::ListUpperAlpha: {
1430 QTextLayout layout(itemText, font, q->paintDevice());
1431 layout.setCacheEnabled(true);
1432 QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute);
1433 option.setTextDirection(dir);
1434 layout.setTextOption(option);
1435 layout.beginLayout();
1436 layout.createLine();
1437 layout.endLayout();
1438 layout.draw(painter, QPointF(r.left(), pos.y()));
1439 break;
1440 }
1441 case QTextListFormat::ListSquare:
1442 painter->fillRect(r, brush);
1443 break;
1444 case QTextListFormat::ListCircle:
1445 painter->drawEllipse(r);
1446 break;
1447 case QTextListFormat::ListDisc:
1448 painter->setBrush(brush);
1449 painter->setPen(Qt::NoPen);
1450 painter->drawEllipse(r);
1451 painter->setBrush(Qt::NoBrush);
1452 break;
1453 case QTextListFormat::ListStyleUndefined:
1454 break;
1455 default:
1456 break;
1457 }
1458
1459 painter->restore();
1460}
1461
1462static QFixed flowPosition(const QTextFrame::iterator it)
1463{
1464 if (it.atEnd())
1465 return 0;
1466
1467 if (it.currentFrame()) {
1468 return data(it.currentFrame())->position.y;
1469 } else {
1470 QTextBlock block = it.currentBlock();
1471 QTextLayout *layout = block.layout();
1472 if (layout->lineCount() == 0)
1473 return QFixed::fromReal(layout->position().y());
1474 else
1475 return QFixed::fromReal(layout->position().y() + layout->lineAt(0).y());
1476 }
1477}
1478
1479static QFixed firstChildPos(const QTextFrame *f)
1480{
1481 return flowPosition(f->begin());
1482}
1483
1484QLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
1485 int layoutFrom, int layoutTo, QTextTableData *td,
1486 QFixed absoluteTableY, bool withPageBreaks)
1487{
1488 LDEBUG << "layoutCell";
1489 QLayoutStruct layoutStruct;
1490 layoutStruct.frame = t;
1491 layoutStruct.minimumWidth = 0;
1492 layoutStruct.maximumWidth = QFIXED_MAX;
1493 layoutStruct.y = 0;
1494
1495 const QTextFormat fmt = cell.format();
1496 const QFixed topPadding = td->topPadding(fmt);
1497 if (withPageBreaks) {
1498 layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding;
1499 }
1500 layoutStruct.x_left = 0;
1501 layoutStruct.x_right = width;
1502 // we get called with different widths all the time (for example for figuring
1503 // out the min/max widths), so we always have to do the full layout ;(
1504 // also when for example in a table layoutFrom/layoutTo affect only one cell,
1505 // making that one cell grow the available width of the other cells may change
1506 // (shrink) and therefore when layoutCell gets called for them they have to
1507 // be re-laid out, even if layoutFrom/layoutTo is not in their range. Hence
1508 // this line:
1509
1510 layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
1511 if (layoutStruct.pageHeight < 0 || !withPageBreaks)
1512 layoutStruct.pageHeight = QFIXED_MAX;
1513 const int currentPage = layoutStruct.currentPage();
1514 layoutStruct.pageTopMargin = td->effectiveTopMargin + td->cellSpacing + td->border + topPadding;
1515 layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->border + td->bottomPadding(fmt);
1516 layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
1517
1518 layoutStruct.fullLayout = true;
1519
1520 QFixed pageTop = currentPage * layoutStruct.pageHeight + layoutStruct.pageTopMargin - layoutStruct.frameY;
1521 layoutStruct.y = qMax(layoutStruct.y, pageTop);
1522
1523 const QList<QTextFrame *> childFrames = td->childFrameMap.values(cell.row() + cell.column() * t->rows());
1524 for (int i = 0; i < childFrames.size(); ++i) {
1525 QTextFrame *frame = childFrames.at(i);
1526 QTextFrameData *cd = data(frame);
1527 cd->sizeDirty = true;
1528 }
1529
1530 layoutFlow(cell.begin(), &layoutStruct, layoutFrom, layoutTo, width);
1531
1532 QFixed floatMinWidth;
1533
1534 // floats that are located inside the text (like inline images) aren't taken into account by
1535 // layoutFlow with regards to the cell height (layoutStruct->y), so for a safety measure we
1536 // do that here. For example with <td><img align="right" src="..." />blah</td>
1537 // when the image happens to be higher than the text
1538 for (int i = 0; i < childFrames.size(); ++i) {
1539 QTextFrame *frame = childFrames.at(i);
1540 QTextFrameData *cd = data(frame);
1541
1542 if (frame->frameFormat().position() != QTextFrameFormat::InFlow)
1543 layoutStruct.y = qMax(layoutStruct.y, cd->position.y + cd->size.height);
1544
1545 floatMinWidth = qMax(floatMinWidth, cd->minimumWidth);
1546 }
1547
1548 // constraint the maximumWidth by the minimum width of the fixed size floats, to
1549 // keep them visible
1550 layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth);
1551
1552 // as floats in cells get added to the table's float list but must not affect
1553 // floats in other cells we must clear the list here.
1554 data(t)->floats.clear();
1555
1556// qDebug() << "layoutCell done";
1557
1558 return layoutStruct;
1559}
1560
1561QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY)
1562{
1563 LDEBUG << "layoutTable";
1564 QTextTableData *td = static_cast<QTextTableData *>(data(table));
1565 Q_ASSERT(td->sizeDirty);
1566 const int rows = table->rows();
1567 const int columns = table->columns();
1568
1569 const QTextTableFormat fmt = table->format();
1570
1571 td->childFrameMap.clear();
1572 {
1573 const QList<QTextFrame *> children = table->childFrames();
1574 for (int i = 0; i < children.count(); ++i) {
1575 QTextFrame *frame = children.at(i);
1576 QTextTableCell cell = table->cellAt(frame->firstPosition());
1577 td->childFrameMap.insertMulti(cell.row() + cell.column() * rows, frame);
1578 }
1579 }
1580
1581 QVector<QTextLength> columnWidthConstraints = fmt.columnWidthConstraints();
1582 if (columnWidthConstraints.size() != columns)
1583 columnWidthConstraints.resize(columns);
1584 Q_ASSERT(columnWidthConstraints.count() == columns);
1585
1586 const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(fmt.cellSpacing()));
1587 td->deviceScale = scaleToDevice(qreal(1));
1588 td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding()));
1589 const QFixed leftMargin = td->leftMargin + td->border + td->padding;
1590 const QFixed rightMargin = td->rightMargin + td->border + td->padding;
1591 const QFixed topMargin = td->topMargin + td->border + td->padding;
1592
1593 const QFixed absoluteTableY = parentY + td->position.y;
1594
1595 const QTextOption::WrapMode oldDefaultWrapMode = docPrivate->defaultTextOption.wrapMode();
1596
1597recalc_minmax_widths:
1598
1599 QFixed remainingWidth = td->contentsWidth;
1600 // two (vertical) borders per cell per column
1601 remainingWidth -= columns * 2 * td->border;
1602 // inter-cell spacing
1603 remainingWidth -= (columns - 1) * cellSpacing;
1604 // cell spacing at the left and right hand side
1605 remainingWidth -= 2 * cellSpacing;
1606 // remember the width used to distribute to percentaged columns
1607 const QFixed initialTotalWidth = remainingWidth;
1608
1609 td->widths.resize(columns);
1610 td->widths.fill(0);
1611
1612 td->minWidths.resize(columns);
1613 // start with a minimum width of 0. totally empty
1614 // cells of default created tables are invisible otherwise
1615 // and therefore hardly editable
1616 td->minWidths.fill(1);
1617
1618 td->maxWidths.resize(columns);
1619 td->maxWidths.fill(QFIXED_MAX);
1620
1621 // calculate minimum and maximum sizes of the columns
1622 for (int i = 0; i < columns; ++i) {
1623 for (int row = 0; row < rows; ++row) {
1624 const QTextTableCell cell = table->cellAt(row, i);
1625 const int cspan = cell.columnSpan();
1626
1627 if (cspan > 1 && i != cell.column())
1628 continue;
1629
1630 const QTextFormat fmt = cell.format();
1631 const QFixed leftPadding = td->leftPadding(fmt);
1632 const QFixed rightPadding = td->rightPadding(fmt);
1633 const QFixed widthPadding = leftPadding + rightPadding;
1634
1635 // to figure out the min and the max width lay out the cell at
1636 // maximum width. otherwise the maxwidth calculation sometimes
1637 // returns wrong values
1638 QLayoutStruct layoutStruct = layoutCell(table, cell, QFIXED_MAX, layoutFrom,
1639 layoutTo, td, absoluteTableY,
1640 /*withPageBreaks =*/false);
1641
1642 // distribute the minimum width over all columns the cell spans
1643 QFixed widthToDistribute = layoutStruct.minimumWidth + widthPadding;
1644 for (int n = 0; n < cspan; ++n) {
1645 const int col = i + n;
1646 QFixed w = widthToDistribute / (cspan - n);
1647 td->minWidths[col] = qMax(td->minWidths.at(col), w);
1648 widthToDistribute -= td->minWidths.at(col);
1649 if (widthToDistribute <= 0)
1650 break;
1651 }
1652
1653 QFixed maxW = td->maxWidths.at(i);
1654 if (layoutStruct.maximumWidth != QFIXED_MAX) {
1655 if (maxW == QFIXED_MAX)
1656 maxW = layoutStruct.maximumWidth + widthPadding;
1657 else
1658 maxW = qMax(maxW, layoutStruct.maximumWidth + widthPadding);
1659 }
1660 if (maxW == QFIXED_MAX)
1661 continue;
1662
1663 widthToDistribute = maxW;
1664 for (int n = 0; n < cspan; ++n) {
1665 const int col = i + n;
1666 QFixed w = widthToDistribute / (cspan - n);
1667 td->maxWidths[col] = qMax(td->minWidths.at(col), w);
1668 widthToDistribute -= td->maxWidths.at(col);
1669 if (widthToDistribute <= 0)
1670 break;
1671 }
1672 }
1673 }
1674
1675 // set fixed values, figure out total percentages used and number of
1676 // variable length cells. Also assign the minimum width for variable columns.
1677 QFixed totalPercentage;
1678 int variableCols = 0;
1679 QFixed totalMinWidth = 0;
1680 for (int i = 0; i < columns; ++i) {
1681 const QTextLength &length = columnWidthConstraints.at(i);
1682 if (length.type() == QTextLength::FixedLength) {
1683 td->minWidths[i] = td->widths[i] = qMax(scaleToDevice(QFixed::fromReal(length.rawValue())), td->minWidths.at(i));
1684 remainingWidth -= td->widths.at(i);
1685 } else if (length.type() == QTextLength::PercentageLength) {
1686 totalPercentage += QFixed::fromReal(length.rawValue());
1687 } else if (length.type() == QTextLength::VariableLength) {
1688 variableCols++;
1689
1690 td->widths[i] = td->minWidths.at(i);
1691 remainingWidth -= td->minWidths.at(i);
1692 }
1693 totalMinWidth += td->minWidths.at(i);
1694 }
1695
1696 // set percentage values
1697 {
1698 const QFixed totalPercentagedWidth = initialTotalWidth * totalPercentage / 100;
1699 QFixed remainingMinWidths = totalMinWidth;
1700 for (int i = 0; i < columns; ++i) {
1701 remainingMinWidths -= td->minWidths.at(i);
1702 if (columnWidthConstraints.at(i).type() == QTextLength::PercentageLength) {
1703 const QFixed allottedPercentage = QFixed::fromReal(columnWidthConstraints.at(i).rawValue());
1704
1705 const QFixed percentWidth = totalPercentagedWidth * allottedPercentage / totalPercentage;
1706 if (percentWidth >= td->minWidths.at(i)) {
1707 td->widths[i] = qBound(td->minWidths.at(i), percentWidth, remainingWidth - remainingMinWidths);
1708 } else {
1709 td->widths[i] = td->minWidths.at(i);
1710 }
1711 remainingWidth -= td->widths.at(i);
1712 }
1713 }
1714 }
1715
1716 // for variable columns distribute the remaining space
1717 if (variableCols > 0 && remainingWidth > 0) {
1718 QVarLengthArray<int> columnsWithProperMaxSize;
1719 for (int i = 0; i < columns; ++i)
1720 if (columnWidthConstraints.at(i).type() == QTextLength::VariableLength
1721 && td->maxWidths.at(i) != QFIXED_MAX)
1722 columnsWithProperMaxSize.append(i);
1723
1724 QFixed lastRemainingWidth = remainingWidth;
1725 while (remainingWidth > 0) {
1726 for (int k = 0; k < columnsWithProperMaxSize.count(); ++k) {
1727 const int col = columnsWithProperMaxSize[k];
1728 const int colsLeft = columnsWithProperMaxSize.count() - k;
1729 const QFixed w = qMin(td->maxWidths.at(col) - td->widths.at(col), remainingWidth / colsLeft);
1730 td->widths[col] += w;
1731 remainingWidth -= w;
1732 }
1733 if (remainingWidth == lastRemainingWidth)
1734 break;
1735 lastRemainingWidth = remainingWidth;
1736 }
1737
1738 if (remainingWidth > 0
1739 // don't unnecessarily grow variable length sized tables
1740 && fmt.width().type() != QTextLength::VariableLength) {
1741 const QFixed widthPerAnySizedCol = remainingWidth / variableCols;
1742 for (int col = 0; col < columns; ++col) {
1743 if (columnWidthConstraints.at(col).type() == QTextLength::VariableLength)
1744 td->widths[col] += widthPerAnySizedCol;
1745 }
1746 }
1747 }
1748
1749 td->columnPositions.resize(columns);
1750 td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border;
1751
1752 for (int i = 1; i < columns; ++i)
1753 td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->border + cellSpacing;
1754
1755 // - margin to compensate the + margin in columnPositions[0]
1756 const QFixed contentsWidth = td->columnPositions.last() + td->widths.last() + td->padding + td->border + cellSpacing - leftMargin;
1757
1758 // if the table is too big and causes an overflow re-do the layout with WrapAnywhere as wrap
1759 // mode
1760 if (docPrivate->defaultTextOption.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere
1761 && contentsWidth > td->contentsWidth) {
1762 docPrivate->defaultTextOption.setWrapMode(QTextOption::WrapAnywhere);
1763 // go back to the top of the function
1764 goto recalc_minmax_widths;
1765 }
1766
1767 td->contentsWidth = contentsWidth;
1768
1769 docPrivate->defaultTextOption.setWrapMode(oldDefaultWrapMode);
1770
1771 td->heights.resize(rows);
1772 td->heights.fill(0);
1773
1774 td->rowPositions.resize(rows);
1775 td->rowPositions[0] = topMargin /*includes table border*/ + cellSpacing + td->border;
1776
1777 bool haveRowSpannedCells = false;
1778
1779 // need to keep track of cell heights for vertical alignment
1780 QVector<QFixed> cellHeights;
1781 cellHeights.reserve(rows * columns);
1782
1783 QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
1784 if (pageHeight <= 0)
1785 pageHeight = QFIXED_MAX;
1786
1787 QVector<QFixed> heightToDistribute;
1788 heightToDistribute.resize(columns);
1789
1790 td->headerHeight = 0;
1791 const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
1792 const QFixed originalTopMargin = td->effectiveTopMargin;
1793 bool hasDroppedTable = false;
1794
1795 // now that we have the column widths we can lay out all cells with the right width.
1796 // spanning cells are only allowed to grow the last row spanned by the cell.
1797 //
1798 // ### this could be made faster by iterating over the cells array of QTextTable
1799 for (int r = 0; r < rows; ++r) {
1800 td->calcRowPosition(r);
1801
1802 const int tableStartPage = (absoluteTableY / pageHeight).truncate();
1803 const int currentPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
1804 const QFixed pageBottom = (currentPage + 1) * pageHeight - td->effectiveBottomMargin - absoluteTableY - cellSpacing - td->border;
1805 const QFixed pageTop = currentPage * pageHeight + td->effectiveTopMargin - absoluteTableY + cellSpacing + td->border;
1806 const QFixed nextPageTop = pageTop + pageHeight;
1807
1808 if (td->rowPositions[r] > pageBottom)
1809 td->rowPositions[r] = nextPageTop;
1810 else if (td->rowPositions[r] < pageTop)
1811 td->rowPositions[r] = pageTop;
1812
1813 bool dropRowToNextPage = true;
1814 int cellCountBeforeRow = cellHeights.size();
1815
1816 // if we drop the row to the next page we need to subtract the drop
1817 // distance from any row spanning cells
1818 QFixed dropDistance = 0;
1819
1820relayout:
1821 const int rowStartPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
1822 // if any of the header rows or the first non-header row start on the next page
1823 // then the entire header should be dropped
1824 if (r <= headerRowCount && rowStartPage > tableStartPage && !hasDroppedTable) {
1825 td->rowPositions[0] = nextPageTop;
1826 cellHeights.clear();
1827 td->effectiveTopMargin = originalTopMargin;
1828 hasDroppedTable = true;
1829 r = -1;
1830 continue;
1831 }
1832
1833 int rowCellCount = 0;
1834 for (int c = 0; c < columns; ++c) {
1835 QTextTableCell cell = table->cellAt(r, c);
1836 const int rspan = cell.rowSpan();
1837 const int cspan = cell.columnSpan();
1838
1839 if (cspan > 1 && cell.column() != c)
1840 continue;
1841
1842 if (rspan > 1) {
1843 haveRowSpannedCells = true;
1844
1845 const int cellRow = cell.row();
1846 if (cellRow != r) {
1847 // the last row gets all the remaining space
1848 if (cellRow + rspan - 1 == r)
1849 td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance);
1850 continue;
1851 }
1852 }
1853
1854 const QTextFormat fmt = cell.format();
1855
1856 const QFixed topPadding = td->topPadding(fmt);
1857 const QFixed bottomPadding = td->bottomPadding(fmt);
1858 const QFixed leftPadding = td->leftPadding(fmt);
1859 const QFixed rightPadding = td->rightPadding(fmt);
1860 const QFixed widthPadding = leftPadding + rightPadding;
1861
1862 ++rowCellCount;
1863
1864 const QFixed width = td->cellWidth(c, cspan) - widthPadding;
1865 QLayoutStruct layoutStruct = layoutCell(table, cell, width,
1866 layoutFrom, layoutTo,
1867 td, absoluteTableY,
1868 /*withPageBreaks =*/true);
1869
1870 const QFixed height = layoutStruct.y + bottomPadding + topPadding;
1871
1872 if (rspan > 1)
1873 heightToDistribute[c] = height + dropDistance;
1874 else
1875 td->heights[r] = qMax(td->heights.at(r), height);
1876
1877 cellHeights.append(layoutStruct.y);
1878
1879 QFixed childPos = td->rowPositions.at(r) + topPadding + flowPosition(cell.begin());
1880 if (childPos < pageBottom)
1881 dropRowToNextPage = false;
1882 }
1883
1884 if (rowCellCount > 0 && dropRowToNextPage) {
1885 dropDistance = nextPageTop - td->rowPositions[r];
1886 td->rowPositions[r] = nextPageTop;
1887 td->heights[r] = 0;
1888 dropRowToNextPage = false;
1889 cellHeights.resize(cellCountBeforeRow);
1890 if (r > headerRowCount)
1891 td->heights[r-1] = pageBottom - td->rowPositions[r-1];
1892 goto relayout;
1893 }
1894
1895 if (haveRowSpannedCells) {
1896 const QFixed effectiveHeight = td->heights.at(r) + td->border + cellSpacing + td->border;
1897 for (int c = 0; c < columns; ++c)
1898 heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0));
1899 }
1900
1901 if (r == headerRowCount - 1) {
1902 td->headerHeight = td->rowPositions[r] + td->heights[r] - td->rowPositions[0] + td->cellSpacing + 2 * td->border;
1903 td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate();
1904 td->effectiveTopMargin += td->headerHeight;
1905 }
1906 }
1907
1908 td->effectiveTopMargin = originalTopMargin;
1909
1910 // now that all cells have been properly laid out, we can compute the
1911 // vertical offsets for vertical alignment
1912 td->cellVerticalOffsets.resize(rows * columns);
1913 int cellIndex = 0;
1914 for (int r = 0; r < rows; ++r) {
1915 for (int c = 0; c < columns; ++c) {
1916 QTextTableCell cell = table->cellAt(r, c);
1917 if (cell.row() != r || cell.column() != c)
1918 continue;
1919
1920 const int rowSpan = cell.rowSpan();
1921 const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r);
1922
1923 const QTextCharFormat cellFormat = cell.format();
1924 const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(cellFormat) + td->bottomPadding(cellFormat);
1925
1926 QFixed offset = 0;
1927 switch (cellFormat.verticalAlignment()) {
1928 case QTextCharFormat::AlignMiddle:
1929 offset = (availableHeight - cellHeight) / 2;
1930 break;
1931 case QTextCharFormat::AlignBottom:
1932 offset = availableHeight - cellHeight;
1933 break;
1934 default:
1935 break;
1936 };
1937
1938 for (int rd = 0; rd < cell.rowSpan(); ++rd) {
1939 for (int cd = 0; cd < cell.columnSpan(); ++cd) {
1940 const int index = (c + cd) + (r + rd) * columns;
1941 td->cellVerticalOffsets[index] = offset;
1942 }
1943 }
1944 }
1945 }
1946
1947 td->minimumWidth = td->columnPositions.at(0);
1948 for (int i = 0; i < columns; ++i) {
1949 td->minimumWidth += td->minWidths.at(i) + 2 * td->border + cellSpacing;
1950 }
1951 td->minimumWidth += rightMargin - td->border;
1952
1953 td->maximumWidth = td->columnPositions.at(0);
1954 for (int i = 0; i < columns; ++i)
1955 if (td->maxWidths.at(i) != QFIXED_MAX)
1956 td->maximumWidth += td->maxWidths.at(i) + 2 * td->border + cellSpacing;
1957 td->maximumWidth += rightMargin - td->border;
1958
1959 td->updateTableSize();
1960 td->sizeDirty = false;
1961 return QRectF(); // invalid rect -> update everything
1962}
1963
1964void QTextDocumentLayoutPrivate::positionFloat(QTextFrame *frame, QTextLine *currentLine)
1965{
1966 QTextFrameData *fd = data(frame);
1967
1968 QTextFrame *parent = frame->parentFrame();
1969 Q_ASSERT(parent);
1970 QTextFrameData *pd = data(parent);
1971 Q_ASSERT(pd && pd->currentLayoutStruct);
1972
1973 QLayoutStruct *layoutStruct = pd->currentLayoutStruct;
1974
1975 if (!pd->floats.contains(frame))
1976 pd->floats.append(frame);
1977 fd->layoutDirty = true;
1978 Q_ASSERT(!fd->sizeDirty);
1979
1980// qDebug() << "positionFloat:" << frame << "width=" << fd->size.width;
1981 QFixed y = layoutStruct->y;
1982 if (currentLine) {
1983 QFixed left, right;
1984 floatMargins(y, layoutStruct, &left, &right);
1985// qDebug() << "have line: right=" << right << "left=" << left << "textWidth=" << currentLine->width();
1986 if (right - left < QFixed::fromReal(currentLine->naturalTextWidth()) + fd->size.width) {
1987 layoutStruct->pendingFloats.append(frame);
1988// qDebug() << " adding to pending list";
1989 return;
1990 }
1991 }
1992
1993 if (y + layoutStruct->frameY + fd->size.height > layoutStruct->pageBottom) {
1994 layoutStruct->newPage();
1995 y = layoutStruct->y;
1996 }
1997
1998 y = findY(y, layoutStruct, fd->size.width);
1999
2000 QFixed left, right;
2001 floatMargins(y, layoutStruct, &left, &right);
2002
2003 if (frame->frameFormat().position() == QTextFrameFormat::FloatLeft) {
2004 fd->position.x = left;
2005 fd->position.y = y;
2006 } else {
2007 fd->position.x = right - fd->size.width;
2008 fd->position.y = y;
2009 }
2010
2011 layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, fd->minimumWidth);
2012 layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, fd->maximumWidth);
2013
2014// qDebug()<< "float positioned at " << fd->position.x << fd->position.y;
2015 fd->layoutDirty = false;
2016}
2017
2018QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY)
2019{
2020 LDEBUG << "layoutFrame (pre)";
2021 Q_ASSERT(data(f)->sizeDirty);
2022// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2023
2024 QTextFrameFormat fformat = f->frameFormat();
2025
2026 QTextFrame *parent = f->parentFrame();
2027 const QTextFrameData *pd = parent ? data(parent) : 0;
2028
2029 const qreal maximumWidth = qMax(qreal(0), pd ? pd->contentsWidth.toReal() : document->pageSize().width());
2030 QFixed width = QFixed::fromReal(fformat.width().value(maximumWidth));
2031 if (fformat.width().type() == QTextLength::FixedLength)
2032 width = scaleToDevice(width);
2033
2034 const QFixed maximumHeight = pd ? pd->contentsHeight : -1;
2035 const QFixed height = (maximumHeight != -1 || fformat.height().type() != QTextLength::PercentageLength)
2036 ? QFixed::fromReal(fformat.height().value(maximumHeight.toReal()))
2037 : -1;
2038
2039 return layoutFrame(f, layoutFrom, layoutTo, width, height, parentY);
2040}
2041
2042QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY)
2043{
2044 LDEBUG << "layoutFrame from=" << layoutFrom << "to=" << layoutTo;
2045 Q_ASSERT(data(f)->sizeDirty);
2046// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2047
2048 QTextFrameData *fd = data(f);
2049 QFixed newContentsWidth;
2050
2051 {
2052 QTextFrameFormat fformat = f->frameFormat();
2053 // set sizes of this frame from the format
2054 fd->topMargin = QFixed::fromReal(fformat.topMargin());
2055 fd->bottomMargin = QFixed::fromReal(fformat.bottomMargin());
2056 fd->leftMargin = QFixed::fromReal(fformat.leftMargin());
2057 fd->rightMargin = QFixed::fromReal(fformat.rightMargin());
2058 fd->border = QFixed::fromReal(fformat.border());
2059 fd->padding = QFixed::fromReal(fformat.padding());
2060
2061 QTextFrame *parent = f->parentFrame();
2062 const QTextFrameData *pd = parent ? data(parent) : 0;
2063
2064 // accumulate top and bottom margins
2065 if (parent) {
2066 fd->effectiveTopMargin = pd->effectiveTopMargin + fd->topMargin + fd->border + fd->padding;
2067 fd->effectiveBottomMargin = pd->effectiveBottomMargin + fd->topMargin + fd->border + fd->padding;
2068
2069 if (qobject_cast<QTextTable *>(parent)) {
2070 const QTextTableData *td = static_cast<const QTextTableData *>(pd);
2071 fd->effectiveTopMargin += td->cellSpacing + td->border + td->cellPadding;
2072 fd->effectiveBottomMargin += td->cellSpacing + td->border + td->cellPadding;
2073 }
2074 } else {
2075 fd->effectiveTopMargin = fd->topMargin + fd->border + fd->padding;
2076 fd->effectiveBottomMargin = fd->bottomMargin + fd->border + fd->padding;
2077 }
2078
2079 newContentsWidth = frameWidth - 2*(fd->border + fd->padding)
2080 - fd->leftMargin - fd->rightMargin;
2081
2082 if (frameHeight != -1) {
2083 fd->contentsHeight = frameHeight - 2*(fd->border + fd->padding)
2084 - fd->topMargin - fd->bottomMargin;
2085 } else {
2086 fd->contentsHeight = frameHeight;
2087 }
2088 }
2089
2090 if (isFrameFromInlineObject(f)) {
2091 // never reached, handled in resizeInlineObject/positionFloat instead
2092 return QRectF();
2093 }
2094
2095 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
2096 fd->contentsWidth = newContentsWidth;
2097 return layoutTable(table, layoutFrom, layoutTo, parentY);
2098 }
2099
2100 // set fd->contentsWidth temporarily, so that layoutFrame for the children
2101 // picks the right width. We'll initialize it properly at the end of this
2102 // function.
2103 fd->contentsWidth = newContentsWidth;
2104
2105 QLayoutStruct layoutStruct;
2106 layoutStruct.frame = f;
2107 layoutStruct.x_left = fd->leftMargin + fd->border + fd->padding;
2108 layoutStruct.x_right = layoutStruct.x_left + newContentsWidth;
2109 layoutStruct.y = fd->topMargin + fd->border + fd->padding;
2110 layoutStruct.frameY = parentY + fd->position.y;
2111 layoutStruct.contentsWidth = 0;
2112 layoutStruct.minimumWidth = 0;
2113 layoutStruct.maximumWidth = QFIXED_MAX;
2114 layoutStruct.fullLayout = fd->oldContentsWidth != newContentsWidth;
2115 layoutStruct.updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
2116 LDEBUG << "layoutStruct: x_left" << layoutStruct.x_left << "x_right" << layoutStruct.x_right
2117 << "fullLayout" << layoutStruct.fullLayout;
2118 fd->oldContentsWidth = newContentsWidth;
2119
2120 layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
2121 if (layoutStruct.pageHeight < 0)
2122 layoutStruct.pageHeight = QFIXED_MAX;
2123
2124 const int currentPage = layoutStruct.pageHeight == 0 ? 0 : (layoutStruct.frameY / layoutStruct.pageHeight).truncate();
2125 layoutStruct.pageTopMargin = fd->effectiveTopMargin;
2126 layoutStruct.pageBottomMargin = fd->effectiveBottomMargin;
2127 layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
2128
2129 if (!f->parentFrame())
2130 idealWidth = 0; // reset
2131
2132 QTextFrame::Iterator it = f->begin();
2133 layoutFlow(it, &layoutStruct, layoutFrom, layoutTo);
2134
2135 QFixed maxChildFrameWidth = 0;
2136 QList<QTextFrame *> children = f->childFrames();
2137 for (int i = 0; i < children.size(); ++i) {
2138 QTextFrame *c = children.at(i);
2139 QTextFrameData *cd = data(c);
2140 maxChildFrameWidth = qMax(maxChildFrameWidth, cd->size.width);
2141 }
2142
2143 const QFixed marginWidth = 2*(fd->border + fd->padding) + fd->leftMargin + fd->rightMargin;
2144 if (!f->parentFrame()) {
2145 idealWidth = qMax(maxChildFrameWidth, layoutStruct.contentsWidth).toReal();
2146 idealWidth += marginWidth.toReal();
2147 }
2148
2149 QFixed actualWidth = qMax(newContentsWidth, qMax(maxChildFrameWidth, layoutStruct.contentsWidth));
2150 fd->contentsWidth = actualWidth;
2151 if (newContentsWidth <= 0) { // nowrap layout?
2152 fd->contentsWidth = newContentsWidth;
2153 }
2154
2155 fd->minimumWidth = layoutStruct.minimumWidth;
2156 fd->maximumWidth = layoutStruct.maximumWidth;
2157
2158 fd->size.height = fd->contentsHeight == -1
2159 ? layoutStruct.y + fd->border + fd->padding + fd->bottomMargin
2160 : fd->contentsHeight + 2*(fd->border + fd->padding) + fd->topMargin + fd->bottomMargin;
2161 fd->size.width = actualWidth + marginWidth;
2162 fd->sizeDirty = false;
2163 if (layoutStruct.updateRectForFloats.isValid())
2164 layoutStruct.updateRect |= layoutStruct.updateRectForFloats;
2165 return layoutStruct.updateRect;
2166}
2167
2168void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QLayoutStruct *layoutStruct,
2169 int layoutFrom, int layoutTo, QFixed width)
2170{
2171 LDEBUG << "layoutFlow from=" << layoutFrom << "to=" << layoutTo;
2172 QTextFrameData *fd = data(layoutStruct->frame);
2173
2174 fd->currentLayoutStruct = layoutStruct;
2175
2176 QTextFrame::Iterator previousIt;
2177
2178 const bool inRootFrame = (it.parentFrame() == document->rootFrame());
2179 if (inRootFrame) {
2180 bool redoCheckPoints = layoutStruct->fullLayout || checkPoints.isEmpty();
2181
2182 if (!redoCheckPoints) {
2183 QVector<QCheckPoint>::Iterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), layoutFrom);
2184 if (checkPoint != checkPoints.end()) {
2185 if (checkPoint != checkPoints.begin())
2186 --checkPoint;
2187
2188 layoutStruct->y = checkPoint->y;
2189 layoutStruct->frameY = checkPoint->frameY;
2190 layoutStruct->minimumWidth = checkPoint->minimumWidth;
2191 layoutStruct->maximumWidth = checkPoint->maximumWidth;
2192 layoutStruct->contentsWidth = checkPoint->contentsWidth;
2193
2194 if (layoutStruct->pageHeight > 0) {
2195 int page = layoutStruct->currentPage();
2196 layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
2197 }
2198
2199 it = frameIteratorForTextPosition(checkPoint->positionInFrame);
2200 checkPoints.resize(checkPoint - checkPoints.begin() + 1);
2201
2202 if (checkPoint != checkPoints.begin()) {
2203 previousIt = it;
2204 --previousIt;
2205 }
2206 } else {
2207 redoCheckPoints = true;
2208 }
2209 }
2210
2211 if (redoCheckPoints) {
2212 checkPoints.clear();
2213 QCheckPoint cp;
2214 cp.y = layoutStruct->y;
2215 cp.frameY = layoutStruct->frameY;
2216 cp.positionInFrame = 0;
2217 cp.minimumWidth = layoutStruct->minimumWidth;
2218 cp.maximumWidth = layoutStruct->maximumWidth;
2219 cp.contentsWidth = layoutStruct->contentsWidth;
2220 checkPoints.append(cp);
2221 }
2222 }
2223
2224 QTextBlockFormat previousBlockFormat = previousIt.currentBlock().blockFormat();
2225
2226 QFixed maximumBlockWidth = 0;
2227 while (!it.atEnd()) {
2228 QTextFrame *c = it.currentFrame();
2229
2230 int docPos;
2231 if (it.currentFrame())
2232 docPos = it.currentFrame()->firstPosition();
2233 else
2234 docPos = it.currentBlock().position();
2235
2236 if (inRootFrame) {
2237 if (qAbs(layoutStruct->y - checkPoints.last().y) > 2000) {
2238 QFixed left, right;
2239 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2240 if (left == layoutStruct->x_left && right == layoutStruct->x_right) {
2241 QCheckPoint p;
2242 p.y = layoutStruct->y;
2243 p.frameY = layoutStruct->frameY;
2244 p.positionInFrame = docPos;
2245 p.minimumWidth = layoutStruct->minimumWidth;
2246 p.maximumWidth = layoutStruct->maximumWidth;
2247 p.contentsWidth = layoutStruct->contentsWidth;
2248 checkPoints.append(p);
2249
2250 if (currentLazyLayoutPosition != -1
2251 && docPos > currentLazyLayoutPosition + lazyLayoutStepSize)
2252 break;
2253
2254 }
2255 }
2256 }
2257
2258 if (c) {
2259 // position child frame
2260 QTextFrameData *cd = data(c);
2261
2262 QTextFrameFormat fformat = c->frameFormat();
2263
2264 if (fformat.position() == QTextFrameFormat::InFlow) {
2265 if (fformat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
2266 layoutStruct->newPage();
2267
2268 QFixed left, right;
2269 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2270 left = qMax(left, layoutStruct->x_left);
2271 right = qMin(right, layoutStruct->x_right);
2272
2273 if (right - left < cd->size.width) {
2274 layoutStruct->y = findY(layoutStruct->y, layoutStruct, cd->size.width);
2275 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2276 }
2277
2278 QFixedPoint pos(left, layoutStruct->y);
2279
2280 Qt::Alignment align = Qt::AlignLeft;
2281
2282 QTextTable *table = qobject_cast<QTextTable *>(c);
2283
2284 if (table)
2285 align = table->format().alignment() & Qt::AlignHorizontal_Mask;
2286
2287 // detect whether we have any alignment in the document that disallows optimizations,
2288 // such as not laying out the document again in a textedit with wrapping disabled.
2289 if (inRootFrame && !(align & Qt::AlignLeft))
2290 contentHasAlignment = true;
2291
2292 cd->position = pos;
2293
2294 if (document->pageSize().height() > 0.0f)
2295 cd->sizeDirty = true;
2296
2297 if (cd->sizeDirty) {
2298 if (width != 0)
2299 layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
2300 else
2301 layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
2302
2303 QFixed absoluteChildPos = table ? pos.y + static_cast<QTextTableData *>(data(table))->rowPositions.at(0) : pos.y + firstChildPos(c);
2304 absoluteChildPos += layoutStruct->frameY;
2305
2306 // drop entire frame to next page if first child of frame is on next page
2307 if (absoluteChildPos > layoutStruct->pageBottom) {
2308 layoutStruct->newPage();
2309 pos.y = layoutStruct->y;
2310
2311 cd->position = pos;
2312 cd->sizeDirty = true;
2313
2314 if (width != 0)
2315 layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
2316 else
2317 layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
2318 }
2319 }
2320
2321 // align only if there is space for alignment
2322 if (right - left > cd->size.width) {
2323 if (align & Qt::AlignRight)
2324 pos.x += layoutStruct->x_right - cd->size.width;
2325 else if (align & Qt::AlignHCenter)
2326 pos.x += (layoutStruct->x_right - cd->size.width) / 2;
2327 }
2328
2329 cd->position = pos;
2330
2331 layoutStruct->y += cd->size.height;
2332 const int page = layoutStruct->currentPage();
2333 layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
2334
2335 cd->layoutDirty = false;
2336
2337 if (c->frameFormat().pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
2338 layoutStruct->newPage();
2339 } else {
2340 QRectF oldFrameRect(cd->position.toPointF(), cd->size.toSizeF());
2341 QRectF updateRect;
2342
2343 if (cd->sizeDirty)
2344 updateRect = layoutFrame(c, layoutFrom, layoutTo);
2345
2346 positionFloat(c);
2347
2348 QRectF frameRect(cd->position.toPointF(), cd->size.toSizeF());
2349
2350 if (frameRect == oldFrameRect && updateRect.isValid())
2351 updateRect.translate(cd->position.toPointF());
2352 else
2353 updateRect = frameRect;
2354
2355 layoutStruct->addUpdateRectForFloat(updateRect);
2356 if (oldFrameRect.isValid())
2357 layoutStruct->addUpdateRectForFloat(oldFrameRect);
2358 }
2359
2360 layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, cd->minimumWidth);
2361 layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, cd->maximumWidth);
2362
2363 previousIt = it;
2364 ++it;
2365 } else {
2366 QTextFrame::Iterator lastIt;
2367 if (!previousIt.atEnd())
2368 lastIt = previousIt;
2369 previousIt = it;
2370 QTextBlock block = it.currentBlock();
2371 ++it;
2372
2373 const QTextBlockFormat blockFormat = block.blockFormat();
2374
2375 if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
2376 layoutStruct->newPage();
2377
2378 const QFixed origY = layoutStruct->y;
2379 const QFixed origPageBottom = layoutStruct->pageBottom;
2380 const QFixed origMaximumWidth = layoutStruct->maximumWidth;
2381 layoutStruct->maximumWidth = 0;
2382
2383 const QTextBlockFormat *previousBlockFormatPtr = 0;
2384 if (lastIt.currentBlock().isValid())
2385 previousBlockFormatPtr = &previousBlockFormat;
2386
2387 // layout and position child block
2388 layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
2389
2390 // detect whether we have any alignment in the document that disallows optimizations,
2391 // such as not laying out the document again in a textedit with wrapping disabled.
2392 if (inRootFrame && !(block.layout()->textOption().alignment() & Qt::AlignLeft))
2393 contentHasAlignment = true;
2394
2395 // if the block right before a table is empty 'hide' it by
2396 // positioning it into the table border
2397 if (isEmptyBlockBeforeTable(block, blockFormat, it)) {
2398 const QTextBlock lastBlock = lastIt.currentBlock();
2399 const qreal lastBlockBottomMargin = lastBlock.isValid() ? lastBlock.blockFormat().bottomMargin() : 0.0f;
2400 layoutStruct->y = origY + QFixed::fromReal(qMax(lastBlockBottomMargin, block.blockFormat().topMargin()));
2401 layoutStruct->pageBottom = origPageBottom;
2402 } else {
2403 // if the block right after a table is empty then 'hide' it, too
2404 if (isEmptyBlockAfterTable(block, lastIt.currentFrame())) {
2405 QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
2406 QTextLayout *layout = block.layout();
2407
2408 QPointF pos((td->position.x + td->size.width).toReal(),
2409 (td->position.y + td->size.height).toReal() - layout->boundingRect().height());
2410
2411 layout->setPosition(pos);
2412 layoutStruct->y = origY;
2413 layoutStruct->pageBottom = origPageBottom;
2414 }
2415
2416 // if the block right after a table starts with a line separator, shift it up by one line
2417 if (isLineSeparatorBlockAfterTable(block, lastIt.currentFrame())) {
2418 QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
2419 QTextLayout *layout = block.layout();
2420
2421 QFixed height = QFixed::fromReal(layout->lineAt(0).height());
2422
2423 if (layoutStruct->pageBottom == origPageBottom) {
2424 layoutStruct->y -= height;
2425 layout->setPosition(layout->position() - QPointF(0, height.toReal()));
2426 } else {
2427 // relayout block to correctly handle page breaks
2428 layoutStruct->y = origY - height;
2429 layoutStruct->pageBottom = origPageBottom;
2430 layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
2431 }
2432
2433 QPointF linePos((td->position.x + td->size.width).toReal(),
2434 (td->position.y + td->size.height - height).toReal());
2435
2436 layout->lineAt(0).setPosition(linePos - layout->position());
2437 }
2438
2439 if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
2440 layoutStruct->newPage();
2441 }
2442
2443 maximumBlockWidth = qMax(maximumBlockWidth, layoutStruct->maximumWidth);
2444 layoutStruct->maximumWidth = origMaximumWidth;
2445 previousBlockFormat = blockFormat;
2446 }
2447 }
2448 if (layoutStruct->maximumWidth == QFIXED_MAX && maximumBlockWidth > 0)
2449 layoutStruct->maximumWidth = maximumBlockWidth;
2450 else
2451 layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maximumBlockWidth);
2452
2453 // a float at the bottom of a frame may make it taller, hence the qMax() for layoutStruct->y.
2454 // we don't need to do it for tables though because floats in tables are per table
2455 // and not per cell and layoutCell already takes care of doing the same as we do here
2456 if (!qobject_cast<QTextTable *>(layoutStruct->frame)) {
2457 QList<QTextFrame *> children = layoutStruct->frame->childFrames();
2458 for (int i = 0; i < children.count(); ++i) {
2459 QTextFrameData *fd = data(children.at(i));
2460 if (!fd->layoutDirty && children.at(i)->frameFormat().position() != QTextFrameFormat::InFlow)
2461 layoutStruct->y = qMax(layoutStruct->y, fd->position.y + fd->size.height);
2462 }
2463 }
2464
2465 if (inRootFrame) {
2466 // we assume that any float is aligned in a way that disallows the optimizations that rely
2467 // on unaligned content.
2468 if (!fd->floats.isEmpty())
2469 contentHasAlignment = true;
2470
2471 if (it.atEnd()) {
2472 //qDebug() << "layout done!";
2473 currentLazyLayoutPosition = -1;
2474 QCheckPoint cp;
2475 cp.y = layoutStruct->y;
2476 cp.positionInFrame = docPrivate->length();
2477 cp.minimumWidth = layoutStruct->minimumWidth;
2478 cp.maximumWidth = layoutStruct->maximumWidth;
2479 cp.contentsWidth = layoutStruct->contentsWidth;
2480 checkPoints.append(cp);
2481 checkPoints.reserve(checkPoints.size());
2482 } else {
2483 currentLazyLayoutPosition = checkPoints.last().positionInFrame;
2484 // #######
2485 //checkPoints.last().positionInFrame = q->document()->docHandle()->length();
2486 }
2487 }
2488
2489
2490 fd->currentLayoutStruct = 0;
2491}
2492
2493void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
2494 QLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat)
2495{
2496 Q_Q(QTextDocumentLayout);
2497
2498 QTextLayout *tl = bl.layout();
2499 const int blockLength = bl.length();
2500
2501 LDEBUG << "layoutBlock from=" << layoutFrom << "to=" << layoutTo;
2502
2503// qDebug() << "layoutBlock; width" << layoutStruct->x_right - layoutStruct->x_left << "(maxWidth is btw" << tl->maximumWidth() << ")";
2504
2505 if (previousBlockFormat) {
2506 qreal margin = qMax(blockFormat.topMargin(), previousBlockFormat->bottomMargin());
2507 if (margin > 0 && q->paintDevice()) {
2508 margin *= qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi());
2509 }
2510 layoutStruct->y += QFixed::fromReal(margin);
2511 }
2512
2513 //QTextFrameData *fd = data(layoutStruct->frame);
2514
2515 Qt::LayoutDirection dir = docPrivate->defaultTextOption.textDirection();
2516 if (blockFormat.hasProperty(QTextFormat::LayoutDirection))
2517 dir = blockFormat.layoutDirection();
2518
2519 QFixed extraMargin;
2520 if (docPrivate->defaultTextOption.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) {
2521 QFontMetricsF fm(bl.charFormat().font());
2522 extraMargin = QFixed::fromReal(fm.width(QChar(QChar(0x21B5))));
2523 }
2524
2525 const QFixed indent = this->blockIndent(blockFormat);
2526 const QFixed totalLeftMargin = QFixed::fromReal(blockFormat.leftMargin()) + (dir == Qt::RightToLeft ? extraMargin : indent);
2527 const QFixed totalRightMargin = QFixed::fromReal(blockFormat.rightMargin()) + (dir == Qt::RightToLeft ? indent : extraMargin);
2528
2529 const QPointF oldPosition = tl->position();
2530 tl->setPosition(QPointF(layoutStruct->x_left.toReal(), layoutStruct->y.toReal()));
2531
2532 if (layoutStruct->fullLayout
2533 || (blockPosition + blockLength > layoutFrom && blockPosition <= layoutTo)
2534 // force relayout if we cross a page boundary
2535 || (layoutStruct->pageHeight != QFIXED_MAX && layoutStruct->absoluteY() + QFixed::fromReal(tl->boundingRect().height()) > layoutStruct->pageBottom)) {
2536
2537 LDEBUG << " do layout";
2538 QTextOption option = docPrivate->defaultTextOption;
2539 option.setTextDirection(dir);
2540 option.setTabs( blockFormat.tabPositions() );
2541
2542 Qt::Alignment align = docPrivate->defaultTextOption.alignment();
2543 if (blockFormat.hasProperty(QTextFormat::BlockAlignment))
2544 align = blockFormat.alignment();
2545 option.setAlignment(QStyle::visualAlignment(dir, align)); // for paragraph that are RTL, alignment is auto-reversed;
2546
2547 if (blockFormat.nonBreakableLines() || document->pageSize().width() < 0) {
2548 option.setWrapMode(QTextOption::ManualWrap);
2549 }
2550
2551 tl->setTextOption(option);
2552
2553 const bool haveWordOrAnyWrapMode = (option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere);
2554
2555// qDebug() << " layouting block at" << bl.position();
2556 const QFixed cy = layoutStruct->y;
2557 const QFixed l = layoutStruct->x_left + totalLeftMargin;
2558 const QFixed r = layoutStruct->x_right - totalRightMargin;
2559
2560 tl->beginLayout();
2561 bool firstLine = true;
2562 while (1) {
2563 QTextLine line = tl->createLine();
2564 if (!line.isValid())
2565 break;
2566
2567 QFixed left, right;
2568 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2569 left = qMax(left, l);
2570 right = qMin(right, r);
2571 QFixed text_indent;
2572 if (firstLine) {
2573 text_indent = QFixed::fromReal(blockFormat.textIndent());
2574 if (dir == Qt::LeftToRight)
2575 left += text_indent;
2576 else
2577 right -= text_indent;
2578 firstLine = false;
2579 }
2580// qDebug() << "layout line y=" << currentYPos << "left=" << left << "right=" <<right;
2581
2582 if (fixedColumnWidth != -1)
2583 line.setNumColumns(fixedColumnWidth, (right - left).toReal());
2584 else
2585 line.setLineWidth((right - left).toReal());
2586
2587// qDebug() << "layoutBlock; layouting line with width" << right - left << "->textWidth" << line.textWidth();
2588 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2589 left = qMax(left, l);
2590 right = qMin(right, r);
2591 if (dir == Qt::LeftToRight)
2592 left += text_indent;
2593 else
2594 right -= text_indent;
2595
2596 if (fixedColumnWidth == -1 && QFixed::fromReal(line.naturalTextWidth()) > right-left) {
2597 // float has been added in the meantime, redo
2598 layoutStruct->pendingFloats.clear();
2599
2600 if (haveWordOrAnyWrapMode) {
2601 option.setWrapMode(QTextOption::WrapAnywhere);
2602 tl->setTextOption(option);
2603 }
2604
2605 line.setLineWidth((right-left).toReal());
2606 if (QFixed::fromReal(line.naturalTextWidth()) > right-left) {
2607 layoutStruct->pendingFloats.clear();
2608 // lines min width more than what we have
2609 layoutStruct->y = findY(layoutStruct->y, layoutStruct, QFixed::fromReal(line.naturalTextWidth()));
2610 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2611 left = qMax(left, l);
2612 right = qMin(right, r);
2613 if (dir == Qt::LeftToRight)
2614 left += text_indent;
2615 else
2616 right -= text_indent;
2617 line.setLineWidth(qMax<qreal>(line.naturalTextWidth(), (right-left).toReal()));
2618 }
2619
2620 if (haveWordOrAnyWrapMode) {
2621 option.setWrapMode(QTextOption::WordWrap);
2622 tl->setTextOption(option);
2623 }
2624 }
2625
2626 QFixed lineHeight = QFixed::fromReal(line.height());
2627 if (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineHeight > layoutStruct->pageBottom) {
2628 layoutStruct->newPage();
2629
2630 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2631 left = qMax(left, l);
2632 right = qMin(right, r);
2633 if (dir == Qt::LeftToRight)
2634 left += text_indent;
2635 else
2636 right -= text_indent;
2637 }
2638
2639 line.setPosition(QPointF((left - layoutStruct->x_left).toReal(), (layoutStruct->y - cy).toReal()));
2640 layoutStruct->y += lineHeight;
2641 layoutStruct->contentsWidth
2642 = qMax<QFixed>(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + line.naturalTextWidth()) + totalRightMargin);
2643
2644 // position floats
2645 for (int i = 0; i < layoutStruct->pendingFloats.size(); ++i) {
2646 QTextFrame *f = layoutStruct->pendingFloats.at(i);
2647 positionFloat(f);
2648 }
2649 layoutStruct->pendingFloats.clear();
2650 }
2651 tl->endLayout();
2652 } else {
2653 const int cnt = tl->lineCount();
2654 for (int i = 0; i < cnt; ++i) {
2655 LDEBUG << "going to move text line" << i;
2656 QTextLine line = tl->lineAt(i);
2657 layoutStruct->contentsWidth
2658 = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + tl->lineAt(i).naturalTextWidth()) + totalRightMargin);
2659 const QFixed lineHeight = QFixed::fromReal(line.height());
2660 if (layoutStruct->pageHeight != QFIXED_MAX) {
2661 if (layoutStruct->absoluteY() + lineHeight > layoutStruct->pageBottom)
2662 layoutStruct->newPage();
2663 line.setPosition(QPointF(line.position().x(), layoutStruct->y.toReal() - tl->position().y()));
2664 }
2665 layoutStruct->y += lineHeight;
2666 }
2667 if (layoutStruct->updateRect.isValid()
2668 && blockLength > 1) {
2669 if (layoutFrom >= blockPosition + blockLength) {
2670 // if our height didn't change and the change in the document is
2671 // in one of the later paragraphs, then we don't need to repaint
2672 // this one
2673 layoutStruct->updateRect.setTop(qMax(layoutStruct->updateRect.top(), layoutStruct->y.toReal()));
2674 } else if (layoutTo < blockPosition) {
2675 if (oldPosition == tl->position())
2676 // if the change in the document happened earlier in the document
2677 // and our position did /not/ change because none of the earlier paragraphs
2678 // or frames changed their height, then we don't need to repaint
2679 // this one
2680 layoutStruct->updateRect.setBottom(qMin(layoutStruct->updateRect.bottom(), tl->position().y()));
2681 else
2682 layoutStruct->updateRect.setBottom(qreal(INT_MAX)); // reset
2683 }
2684 }
2685 }
2686
2687 // ### doesn't take floats into account. would need to do it per line. but how to retrieve then? (Simon)
2688 const QFixed margins = totalLeftMargin + totalRightMargin;
2689 layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, QFixed::fromReal(tl->minimumWidth()) + margins);
2690
2691 const QFixed maxW = QFixed::fromReal(tl->maximumWidth()) + margins;
2692
2693 if (maxW > 0) {
2694 if (layoutStruct->maximumWidth == QFIXED_MAX)
2695 layoutStruct->maximumWidth = maxW;
2696 else
2697 layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maxW);
2698 }
2699}
2700
2701void QTextDocumentLayoutPrivate::floatMargins(const QFixed &y, const QLayoutStruct *layoutStruct,
2702 QFixed *left, QFixed *right) const
2703{
2704// qDebug() << "floatMargins y=" << y;
2705 *left = layoutStruct->x_left;
2706 *right = layoutStruct->x_right;
2707 QTextFrameData *lfd = data(layoutStruct->frame);
2708 for (int i = 0; i < lfd->floats.size(); ++i) {
2709 QTextFrameData *fd = data(lfd->floats.at(i));
2710 if (!fd->layoutDirty) {
2711 if (fd->position.y <= y && fd->position.y + fd->size.height > y) {
2712// qDebug() << "adjusting with float" << f << fd->position.x()<< fd->size.width();
2713 if (lfd->floats.at(i)->frameFormat().position() == QTextFrameFormat::FloatLeft)
2714 *left = qMax(*left, fd->position.x + fd->size.width);
2715 else
2716 *right = qMin(*right, fd->position.x);
2717 }
2718 }
2719 }
2720// qDebug() << "floatMargins: left="<<*left<<"right="<<*right<<"y="<<y;
2721}
2722
2723QFixed QTextDocumentLayoutPrivate::findY(QFixed yFrom, const QLayoutStruct *layoutStruct, QFixed requiredWidth) const
2724{
2725 QFixed right, left;
2726 requiredWidth = qMin(requiredWidth, layoutStruct->x_right - layoutStruct->x_left);
2727
2728// qDebug() << "findY:" << yFrom;
2729 while (1) {
2730 floatMargins(yFrom, layoutStruct, &left, &right);
2731// qDebug() << " yFrom=" << yFrom<<"right=" << right << "left=" << left << "requiredWidth=" << requiredWidth;
2732 if (right-left >= requiredWidth)
2733 break;
2734
2735 // move float down until we find enough space
2736 QFixed newY = QFIXED_MAX;
2737 QTextFrameData *lfd = data(layoutStruct->frame);
2738 for (int i = 0; i < lfd->floats.size(); ++i) {
2739 QTextFrameData *fd = data(lfd->floats.at(i));
2740 if (!fd->layoutDirty) {
2741 if (fd->position.y <= yFrom && fd->position.y + fd->size.height > yFrom)
2742 newY = qMin(newY, fd->position.y + fd->size.height);
2743 }
2744 }
2745 if (newY == QFIXED_MAX)
2746 break;
2747 yFrom = newY;
2748 }
2749 return yFrom;
2750}
2751
2752QTextDocumentLayout::QTextDocumentLayout(QTextDocument *doc)
2753 : QAbstractTextDocumentLayout(*new QTextDocumentLayoutPrivate, doc)
2754{
2755 registerHandler(QTextFormat::ImageObject, new QTextImageHandler(this));
2756}
2757
2758
2759void QTextDocumentLayout::draw(QPainter *painter, const PaintContext &context)
2760{
2761 Q_D(QTextDocumentLayout);
2762 QTextFrame *frame = d->document->rootFrame();
2763 QTextFrameData *fd = data(frame);
2764
2765 if(fd->sizeDirty)
2766 return;
2767
2768 if (context.clip.isValid()) {
2769 d->ensureLayouted(QFixed::fromReal(context.clip.bottom()));
2770 } else {
2771 d->ensureLayoutFinished();
2772 }
2773
2774 QFixed width = fd->size.width;
2775 if (d->document->pageSize().width() == 0 && d->viewportRect.isValid()) {
2776 // we're in NoWrap mode, meaning the frame should expand to the viewport
2777 // so that backgrounds are drawn correctly
2778 fd->size.width = qMax(width, QFixed::fromReal(d->viewportRect.right()));
2779 }
2780
2781 // Make sure we conform to the root frames bounds when drawing.
2782 d->clipRect = QRectF(fd->position.toPointF(), fd->size.toSizeF()).adjusted(fd->leftMargin.toReal(), 0, -fd->rightMargin.toReal(), 0);
2783 d->drawFrame(QPointF(), painter, context, frame);
2784 fd->size.width = width;
2785}
2786
2787void QTextDocumentLayout::setViewport(const QRectF &viewport)
2788{
2789 Q_D(QTextDocumentLayout);
2790 d->viewportRect = viewport;
2791}
2792
2793static void markFrames(QTextFrame *current, int from, int oldLength, int length)
2794{
2795 int end = qMax(oldLength, length) + from;
2796
2797 if (current->firstPosition() >= end || current->lastPosition() < from)
2798 return;
2799
2800 QTextFrameData *fd = data(current);
2801 for (int i = 0; i < fd->floats.size(); ++i) {
2802 QTextFrame *f = fd->floats[i];
2803 if (!f) {
2804 // float got removed in editing operation
2805 fd->floats.removeAt(i);
2806 --i;
2807 }
2808 }
2809
2810 fd->layoutDirty = true;
2811 fd->sizeDirty = true;
2812
2813// qDebug(" marking frame (%d--%d) as dirty", current->firstPosition(), current->lastPosition());
2814 QList<QTextFrame *> children = current->childFrames();
2815 for (int i = 0; i < children.size(); ++i)
2816 markFrames(children.at(i), from, oldLength, length);
2817}
2818
2819void QTextDocumentLayout::documentChanged(int from, int oldLength, int length)
2820{
2821 Q_D(QTextDocumentLayout);
2822
2823 QTextBlock blockIt = document()->findBlock(from);
2824 QTextBlock endIt = document()->findBlock(qMax(0, from + length - 1));
2825 if (endIt.isValid())
2826 endIt = endIt.next();
2827 for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next())
2828 blockIt.clearLayout();
2829
2830 if (d->docPrivate->pageSize.isNull())
2831 return;
2832
2833 QRectF updateRect;
2834
2835 d->lazyLayoutStepSize = 1000;
2836 d->sizeChangedTimer.stop();
2837 d->insideDocumentChange = true;
2838
2839 const int documentLength = d->docPrivate->length();
2840 const bool fullLayout = (oldLength == 0 && length == documentLength);
2841 const bool smallChange = documentLength > 0
2842 && (qMax(length, oldLength) * 100 / documentLength) < 5;
2843
2844 // don't show incremental layout progress (avoid scroll bar flicker)
2845 // if we see only a small change in the document and we're either starting
2846 // a layout run or we're already in progress for that and we haven't seen
2847 // any bigger change previously (showLayoutProgress already false)
2848 if (smallChange
2849 && (d->currentLazyLayoutPosition == -1 || d->showLayoutProgress == false))
2850 d->showLayoutProgress = false;
2851 else
2852 d->showLayoutProgress = true;
2853
2854 if (fullLayout) {
2855 d->contentHasAlignment = false;
2856 d->currentLazyLayoutPosition = 0;
2857 d->checkPoints.clear();
2858 d->layoutStep();
2859 } else {
2860 d->ensureLayoutedByPosition(from);
2861 updateRect = doLayout(from, oldLength, length);
2862 }
2863
2864 if (!d->layoutTimer.isActive() && d->currentLazyLayoutPosition != -1)
2865 d->layoutTimer.start(10, this);
2866
2867 d->insideDocumentChange = false;
2868
2869 if (d->showLayoutProgress) {
2870 const QSizeF newSize = dynamicDocumentSize();
2871 if (newSize != d->lastReportedSize) {
2872 d->lastReportedSize = newSize;
2873 emit documentSizeChanged(newSize);
2874 }
2875 }
2876
2877 if (!updateRect.isValid()) {
2878 // don't use the frame size, it might have shrunken
2879 updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
2880 }
2881
2882 emit update(updateRect);
2883}
2884
2885QRectF QTextDocumentLayout::doLayout(int from, int oldLength, int length)
2886{
2887 Q_D(QTextDocumentLayout);
2888
2889// qDebug("documentChange: from=%d, oldLength=%d, length=%d", from, oldLength, length);
2890
2891 // mark all frames between f_start and f_end as dirty
2892 markFrames(d->docPrivate->rootFrame(), from, oldLength, length);
2893
2894 QRectF updateRect;
2895
2896 QTextFrame *root = d->docPrivate->rootFrame();
2897 if(data(root)->sizeDirty)
2898 updateRect = d->layoutFrame(root, from, from + length);
2899 data(root)->layoutDirty = false;
2900
2901 if (d->currentLazyLayoutPosition == -1)
2902 layoutFinished();
2903 else if (d->showLayoutProgress)
2904 d->sizeChangedTimer.start(0, this);
2905
2906 return updateRect;
2907}
2908
2909int QTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
2910{
2911 Q_D(const QTextDocumentLayout);
2912 d->ensureLayouted(QFixed::fromReal(point.y()));
2913 QTextFrame *f = d->docPrivate->rootFrame();
2914 int position = 0;
2915 QTextLayout *l = 0;
2916 QFixedPoint pointf;
2917 pointf.x = QFixed::fromReal(point.x());
2918 pointf.y = QFixed::fromReal(point.y());
2919 QTextDocumentLayoutPrivate::HitPoint p = d->hitTest(f, pointf, &position, &l, accuracy);
2920 if (accuracy == Qt::ExactHit && p < QTextDocumentLayoutPrivate::PointExact)
2921 return -1;
2922
2923 // ensure we stay within document bounds
2924 int lastPos = f->lastPosition();
2925 if (l && !l->preeditAreaText().isEmpty())
2926 lastPos += l->preeditAreaText().length();
2927 if (position > lastPos)
2928 position = lastPos;
2929 else if (position < 0)
2930 position = 0;
2931
2932 return position;
2933}
2934
2935void QTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
2936{
2937 Q_D(QTextDocumentLayout);
2938 QTextCharFormat f = format.toCharFormat();
2939 Q_ASSERT(f.isValid());
2940 QTextObjectHandler handler = d->handlers.value(f.objectType());
2941 if (!handler.component)
2942 return;
2943
2944 QSizeF intrinsic = handler.iface->intrinsicSize(d->document, posInDocument, format);
2945
2946 QTextFrameFormat::Position pos = QTextFrameFormat::InFlow;
2947 QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
2948 if (frame) {
2949 pos = frame->frameFormat().position();
2950 QTextFrameData *fd = data(frame);
2951 fd->sizeDirty = false;
2952 fd->size = QFixedSize::fromSizeF(intrinsic);
2953 fd->minimumWidth = fd->maximumWidth = fd->size.width;
2954 }
2955
2956 QSizeF inlineSize = (pos == QTextFrameFormat::InFlow ? intrinsic : QSizeF(0, 0));
2957 item.setWidth(inlineSize.width());
2958 if (f.verticalAlignment() == QTextCharFormat::AlignMiddle) {
2959 item.setDescent(inlineSize.height() / 2);
2960 item.setAscent(inlineSize.height() / 2 - 1);
2961 } else {
2962 item.setDescent(0);
2963 item.setAscent(inlineSize.height() - 1);
2964 }
2965}
2966
2967void QTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
2968{
2969 Q_D(QTextDocumentLayout);
2970 Q_UNUSED(posInDocument);
2971 if (item.width() != 0)
2972 // inline
2973 return;
2974
2975 QTextCharFormat f = format.toCharFormat();
2976 Q_ASSERT(f.isValid());
2977 QTextObjectHandler handler = d->handlers.value(f.objectType());
2978 if (!handler.component)
2979 return;
2980
2981 QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
2982 if (!frame)
2983 return;
2984
2985 QTextBlock b = d->document->findBlock(frame->firstPosition());
2986 QTextLine line;
2987 if (b.position() <= frame->firstPosition() && b.position() + b.length() > frame->lastPosition())
2988 line = b.layout()->lineAt(b.layout()->lineCount()-1);
2989// qDebug() << "layoutObject: line.isValid" << line.isValid() << b.position() << b.length() <<
2990// frame->firstPosition() << frame->lastPosition();
2991 d->positionFloat(frame, line.isValid() ? &line : 0);
2992}
2993
2994void QTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item,
2995 int posInDocument, const QTextFormat &format)
2996{
2997 Q_D(QTextDocumentLayout);
2998 QTextCharFormat f = format.toCharFormat();
2999 Q_ASSERT(f.isValid());
3000 QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
3001 if (frame && frame->frameFormat().position() != QTextFrameFormat::InFlow)
3002 return; // don't draw floating frames from inline objects here but in drawFlow instead
3003
3004// qDebug() << "drawObject at" << r;
3005 QAbstractTextDocumentLayout::drawInlineObject(p, rect, item, posInDocument, format);
3006}
3007
3008int QTextDocumentLayout::dynamicPageCount() const
3009{
3010 Q_D(const QTextDocumentLayout);
3011 const QSizeF pgSize = d->document->pageSize();
3012 if (pgSize.height() < 0)
3013 return 1;
3014 return qCeil(dynamicDocumentSize().height() / pgSize.height());
3015}
3016
3017QSizeF QTextDocumentLayout::dynamicDocumentSize() const
3018{
3019 Q_D(const QTextDocumentLayout);
3020 return data(d->docPrivate->rootFrame())->size.toSizeF();
3021}
3022
3023int QTextDocumentLayout::pageCount() const
3024{
3025 Q_D(const QTextDocumentLayout);
3026 d->ensureLayoutFinished();
3027 return dynamicPageCount();
3028}
3029
3030QSizeF QTextDocumentLayout::documentSize() const
3031{
3032 Q_D(const QTextDocumentLayout);
3033 d->ensureLayoutFinished();
3034 return dynamicDocumentSize();
3035}
3036
3037void QTextDocumentLayoutPrivate::ensureLayouted(QFixed y) const
3038{
3039 Q_Q(const QTextDocumentLayout);
3040 if (currentLazyLayoutPosition == -1)
3041 return;
3042 const QSizeF oldSize = q->dynamicDocumentSize();
3043
3044 if (checkPoints.isEmpty())
3045 layoutStep();
3046
3047 while (currentLazyLayoutPosition != -1
3048 && checkPoints.last().y < y)
3049 layoutStep();
3050}
3051
3052void QTextDocumentLayoutPrivate::ensureLayoutedByPosition(int position) const
3053{
3054 if (currentLazyLayoutPosition == -1)
3055 return;
3056 if (position < currentLazyLayoutPosition)
3057 return;
3058 while (currentLazyLayoutPosition != -1
3059 && currentLazyLayoutPosition < position) {
3060 const_cast<QTextDocumentLayout *>(q_func())->doLayout(currentLazyLayoutPosition, 0, INT_MAX - currentLazyLayoutPosition);
3061 }
3062}
3063
3064void QTextDocumentLayoutPrivate::layoutStep() const
3065{
3066 ensureLayoutedByPosition(currentLazyLayoutPosition + lazyLayoutStepSize);
3067 lazyLayoutStepSize = qMin(200000, lazyLayoutStepSize * 2);
3068}
3069
3070void QTextDocumentLayout::setCursorWidth(int width)
3071{
3072 Q_D(QTextDocumentLayout);
3073 d->cursorWidth = width;
3074}
3075
3076int QTextDocumentLayout::cursorWidth() const
3077{
3078 Q_D(const QTextDocumentLayout);
3079 return d->cursorWidth;
3080}
3081
3082void QTextDocumentLayout::setFixedColumnWidth(int width)
3083{
3084 Q_D(QTextDocumentLayout);
3085 d->fixedColumnWidth = width;
3086}
3087
3088QRectF QTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
3089{
3090 Q_D(const QTextDocumentLayout);
3091 if (d->docPrivate->pageSize.isNull())
3092 return QRectF();
3093 d->ensureLayoutFinished();
3094 return d->frameBoundingRectInternal(frame);
3095}
3096
3097QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame) const
3098{
3099 QPointF pos;
3100 const int framePos = frame->firstPosition();
3101 QTextFrame *f = frame;
3102 while (f) {
3103 QTextFrameData *fd = data(f);
3104 pos += fd->position.toPointF();
3105
3106 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
3107 QTextTableCell cell = table->cellAt(framePos);
3108 if (cell.isValid())
3109 pos += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
3110 }
3111
3112 f = f->parentFrame();
3113 }
3114 return QRectF(pos, data(frame)->size.toSizeF());
3115}
3116
3117QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
3118{
3119 Q_D(const QTextDocumentLayout);
3120 if (d->docPrivate->pageSize.isNull())
3121 return QRectF();
3122 d->ensureLayoutedByPosition(block.position() + block.length());
3123 QTextFrame *frame = d->document->frameAt(block.position());
3124 QPointF offset;
3125 const int blockPos = block.position();
3126
3127 while (frame) {
3128 QTextFrameData *fd = data(frame);
3129 offset += fd->position.toPointF();
3130
3131 if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
3132 QTextTableCell cell = table->cellAt(blockPos);
3133 if (cell.isValid())
3134 offset += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
3135 }
3136
3137 frame = frame->parentFrame();
3138 }
3139
3140 const QTextLayout *layout = block.layout();
3141 QRectF rect = layout->boundingRect();
3142 rect.moveTopLeft(layout->position() + offset);
3143 return rect;
3144}
3145
3146int QTextDocumentLayout::layoutStatus() const
3147{
3148 Q_D(const QTextDocumentLayout);
3149 int pos = d->currentLazyLayoutPosition;
3150 if (pos == -1)
3151 return 100;
3152 return pos * 100 / d->document->docHandle()->length();
3153}
3154
3155void QTextDocumentLayout::timerEvent(QTimerEvent *e)
3156{
3157 Q_D(QTextDocumentLayout);
3158 if (e->timerId() == d->layoutTimer.timerId()) {
3159 if (d->currentLazyLayoutPosition != -1)
3160 d->layoutStep();
3161 } else if (e->timerId() == d->sizeChangedTimer.timerId()) {
3162 d->lastReportedSize = dynamicDocumentSize();
3163 emit documentSizeChanged(d->lastReportedSize);
3164 d->sizeChangedTimer.stop();
3165
3166 if (d->currentLazyLayoutPosition == -1) {
3167 const int newCount = dynamicPageCount();
3168 if (newCount != d->lastPageCount) {
3169 d->lastPageCount = newCount;
3170 emit pageCountChanged(newCount);
3171 }
3172 }
3173 } else {
3174 QAbstractTextDocumentLayout::timerEvent(e);
3175 }
3176}
3177
3178void QTextDocumentLayout::layoutFinished()
3179{
3180 Q_D(QTextDocumentLayout);
3181 d->layoutTimer.stop();
3182 if (!d->insideDocumentChange)
3183 d->sizeChangedTimer.start(0, this);
3184 // reset
3185 d->showLayoutProgress = true;
3186}
3187
3188void QTextDocumentLayout::ensureLayouted(qreal y)
3189{
3190 d_func()->ensureLayouted(QFixed::fromReal(y));
3191}
3192
3193qreal QTextDocumentLayout::idealWidth() const
3194{
3195 Q_D(const QTextDocumentLayout);
3196 d->ensureLayoutFinished();
3197 return d->idealWidth;
3198}
3199
3200bool QTextDocumentLayout::contentHasAlignment() const
3201{
3202 Q_D(const QTextDocumentLayout);
3203 return d->contentHasAlignment;
3204}
3205
3206qreal QTextDocumentLayoutPrivate::scaleToDevice(qreal value) const
3207{
3208 QPaintDevice *dev = q_func()->paintDevice();
3209 if (!dev)
3210 return value;
3211 return value * dev->logicalDpiY() / qreal(qt_defaultDpi());
3212}
3213
3214QFixed QTextDocumentLayoutPrivate::scaleToDevice(QFixed value) const
3215{
3216 QPaintDevice *dev = q_func()->paintDevice();
3217 if (!dev)
3218 return value;
3219 return value * QFixed(dev->logicalDpiY()) / QFixed(qt_defaultDpi());
3220}
3221
3222QT_END_NAMESPACE
3223
3224#include "moc_qtextdocumentlayout_p.cpp"
Note: See TracBrowser for help on using the repository browser.