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

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 124.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this 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 have questions regarding the use of this file, please contact
37** Nokia 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
78Q_GUI_EXPORT extern int qt_defaultDpi();
79
80// ################ should probably add frameFormatChange notification!
81
82struct QTextLayoutStruct;
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 QTextLayoutStruct *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 QTextLayoutStruct {
127 QTextLayoutStruct() : 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.userType() == QVariant::Double || v.userType() == QMetaType::Float);
202 return QFixed::fromReal(v.toReal() * 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
330Optimization 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 QTextLayoutStruct 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 QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat);
494 void layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0);
495 void pageBreakInsideTable(QTextTable *table, QTextLayoutStruct *layoutStruct);
496
497
498 void floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const;
499 QFixed findY(QFixed yFrom, const QTextLayoutStruct *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
859 const QBrush bg = frame->frameFormat().background();
860 if (bg != Qt::NoBrush) {
861 QRectF bgRect = rect;
862 bgRect.adjust((fd->leftMargin + fd->border).toReal(),
863 (fd->topMargin + fd->border).toReal(),
864 - (fd->rightMargin + fd->border).toReal(),
865 - (fd->bottomMargin + fd->border).toReal());
866
867 QRectF gradientRect; // invalid makes it default to bgRect
868 QPointF origin = bgRect.topLeft();
869 if (!frame->parentFrame()) {
870 bgRect = clip;
871 gradientRect.setWidth(painter->device()->width());
872 gradientRect.setHeight(painter->device()->height());
873 }
874 fillBackground(painter, bgRect, bg, origin, gradientRect);
875 }
876 if (fd->border != 0) {
877 painter->save();
878 painter->setBrush(Qt::lightGray);
879 painter->setPen(Qt::NoPen);
880
881 const qreal leftEdge = rect.left() + fd->leftMargin.toReal();
882 const qreal border = fd->border.toReal();
883 const qreal topMargin = fd->topMargin.toReal();
884 const qreal leftMargin = fd->leftMargin.toReal();
885 const qreal bottomMargin = fd->bottomMargin.toReal();
886 const qreal rightMargin = fd->rightMargin.toReal();
887 const qreal w = rect.width() - 2 * border - leftMargin - rightMargin;
888 const qreal h = rect.height() - 2 * border - topMargin - bottomMargin;
889
890 drawBorder(painter, QRectF(leftEdge, rect.top() + topMargin, w + border, h + border),
891 fd->effectiveTopMargin.toReal(), fd->effectiveBottomMargin.toReal(),
892 border, frame->frameFormat().borderBrush(), frame->frameFormat().borderStyle());
893
894 painter->restore();
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 = bl.textDirection();
1373 {
1374 QRectF textRect = firstLine.naturalTextRect();
1375 pos += textRect.topLeft().toPoint();
1376 if (dir == Qt::RightToLeft)
1377 pos.rx() += textRect.width();
1378 }
1379
1380 switch (style) {
1381 case QTextListFormat::ListDecimal:
1382 case QTextListFormat::ListLowerAlpha:
1383 case QTextListFormat::ListUpperAlpha:
1384 case QTextListFormat::ListLowerRoman:
1385 case QTextListFormat::ListUpperRoman:
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 case QTextListFormat::ListLowerRoman:
1431 case QTextListFormat::ListUpperRoman: {
1432 QTextLayout layout(itemText, font, q->paintDevice());
1433 layout.setCacheEnabled(true);
1434 QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute);
1435 option.setTextDirection(dir);
1436 layout.setTextOption(option);
1437 layout.beginLayout();
1438 QTextLine line = layout.createLine();
1439 if (line.isValid())
1440 line.setLeadingIncluded(true);
1441 layout.endLayout();
1442 layout.draw(painter, QPointF(r.left(), pos.y()));
1443 break;
1444 }
1445 case QTextListFormat::ListSquare:
1446 painter->fillRect(r, brush);
1447 break;
1448 case QTextListFormat::ListCircle:
1449 painter->setPen(QPen(brush, 0));
1450 painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering
1451 break;
1452 case QTextListFormat::ListDisc:
1453 painter->setBrush(brush);
1454 painter->setPen(Qt::NoPen);
1455 painter->drawEllipse(r);
1456 break;
1457 case QTextListFormat::ListStyleUndefined:
1458 break;
1459 default:
1460 break;
1461 }
1462
1463 painter->restore();
1464}
1465
1466static QFixed flowPosition(const QTextFrame::iterator it)
1467{
1468 if (it.atEnd())
1469 return 0;
1470
1471 if (it.currentFrame()) {
1472 return data(it.currentFrame())->position.y;
1473 } else {
1474 QTextBlock block = it.currentBlock();
1475 QTextLayout *layout = block.layout();
1476 if (layout->lineCount() == 0)
1477 return QFixed::fromReal(layout->position().y());
1478 else
1479 return QFixed::fromReal(layout->position().y() + layout->lineAt(0).y());
1480 }
1481}
1482
1483static QFixed firstChildPos(const QTextFrame *f)
1484{
1485 return flowPosition(f->begin());
1486}
1487
1488QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
1489 int layoutFrom, int layoutTo, QTextTableData *td,
1490 QFixed absoluteTableY, bool withPageBreaks)
1491{
1492 LDEBUG << "layoutCell";
1493 QTextLayoutStruct layoutStruct;
1494 layoutStruct.frame = t;
1495 layoutStruct.minimumWidth = 0;
1496 layoutStruct.maximumWidth = QFIXED_MAX;
1497 layoutStruct.y = 0;
1498
1499 const QTextFormat fmt = cell.format();
1500 const QFixed topPadding = td->topPadding(fmt);
1501 if (withPageBreaks) {
1502 layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding;
1503 }
1504 layoutStruct.x_left = 0;
1505 layoutStruct.x_right = width;
1506 // we get called with different widths all the time (for example for figuring
1507 // out the min/max widths), so we always have to do the full layout ;(
1508 // also when for example in a table layoutFrom/layoutTo affect only one cell,
1509 // making that one cell grow the available width of the other cells may change
1510 // (shrink) and therefore when layoutCell gets called for them they have to
1511 // be re-laid out, even if layoutFrom/layoutTo is not in their range. Hence
1512 // this line:
1513
1514 layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
1515 if (layoutStruct.pageHeight < 0 || !withPageBreaks)
1516 layoutStruct.pageHeight = QFIXED_MAX;
1517 const int currentPage = layoutStruct.currentPage();
1518 layoutStruct.pageTopMargin = td->effectiveTopMargin + td->cellSpacing + td->border + topPadding;
1519 layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->border + td->bottomPadding(fmt);
1520 layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
1521
1522 layoutStruct.fullLayout = true;
1523
1524 QFixed pageTop = currentPage * layoutStruct.pageHeight + layoutStruct.pageTopMargin - layoutStruct.frameY;
1525 layoutStruct.y = qMax(layoutStruct.y, pageTop);
1526
1527 const QList<QTextFrame *> childFrames = td->childFrameMap.values(cell.row() + cell.column() * t->rows());
1528 for (int i = 0; i < childFrames.size(); ++i) {
1529 QTextFrame *frame = childFrames.at(i);
1530 QTextFrameData *cd = data(frame);
1531 cd->sizeDirty = true;
1532 }
1533
1534 layoutFlow(cell.begin(), &layoutStruct, layoutFrom, layoutTo, width);
1535
1536 QFixed floatMinWidth;
1537
1538 // floats that are located inside the text (like inline images) aren't taken into account by
1539 // layoutFlow with regards to the cell height (layoutStruct->y), so for a safety measure we
1540 // do that here. For example with <td><img align="right" src="..." />blah</td>
1541 // when the image happens to be higher than the text
1542 for (int i = 0; i < childFrames.size(); ++i) {
1543 QTextFrame *frame = childFrames.at(i);
1544 QTextFrameData *cd = data(frame);
1545
1546 if (frame->frameFormat().position() != QTextFrameFormat::InFlow)
1547 layoutStruct.y = qMax(layoutStruct.y, cd->position.y + cd->size.height);
1548
1549 floatMinWidth = qMax(floatMinWidth, cd->minimumWidth);
1550 }
1551
1552 // constraint the maximumWidth by the minimum width of the fixed size floats, to
1553 // keep them visible
1554 layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth);
1555
1556 // as floats in cells get added to the table's float list but must not affect
1557 // floats in other cells we must clear the list here.
1558 data(t)->floats.clear();
1559
1560// qDebug() << "layoutCell done";
1561
1562 return layoutStruct;
1563}
1564
1565QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY)
1566{
1567 LDEBUG << "layoutTable";
1568 QTextTableData *td = static_cast<QTextTableData *>(data(table));
1569 Q_ASSERT(td->sizeDirty);
1570 const int rows = table->rows();
1571 const int columns = table->columns();
1572
1573 const QTextTableFormat fmt = table->format();
1574
1575 td->childFrameMap.clear();
1576 {
1577 const QList<QTextFrame *> children = table->childFrames();
1578 for (int i = 0; i < children.count(); ++i) {
1579 QTextFrame *frame = children.at(i);
1580 QTextTableCell cell = table->cellAt(frame->firstPosition());
1581 td->childFrameMap.insertMulti(cell.row() + cell.column() * rows, frame);
1582 }
1583 }
1584
1585 QVector<QTextLength> columnWidthConstraints = fmt.columnWidthConstraints();
1586 if (columnWidthConstraints.size() != columns)
1587 columnWidthConstraints.resize(columns);
1588 Q_ASSERT(columnWidthConstraints.count() == columns);
1589
1590 const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(fmt.cellSpacing()));
1591 td->deviceScale = scaleToDevice(qreal(1));
1592 td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding()));
1593 const QFixed leftMargin = td->leftMargin + td->border + td->padding;
1594 const QFixed rightMargin = td->rightMargin + td->border + td->padding;
1595 const QFixed topMargin = td->topMargin + td->border + td->padding;
1596
1597 const QFixed absoluteTableY = parentY + td->position.y;
1598
1599 const QTextOption::WrapMode oldDefaultWrapMode = docPrivate->defaultTextOption.wrapMode();
1600
1601recalc_minmax_widths:
1602
1603 QFixed remainingWidth = td->contentsWidth;
1604 // two (vertical) borders per cell per column
1605 remainingWidth -= columns * 2 * td->border;
1606 // inter-cell spacing
1607 remainingWidth -= (columns - 1) * cellSpacing;
1608 // cell spacing at the left and right hand side
1609 remainingWidth -= 2 * cellSpacing;
1610 // remember the width used to distribute to percentaged columns
1611 const QFixed initialTotalWidth = remainingWidth;
1612
1613 td->widths.resize(columns);
1614 td->widths.fill(0);
1615
1616 td->minWidths.resize(columns);
1617 // start with a minimum width of 0. totally empty
1618 // cells of default created tables are invisible otherwise
1619 // and therefore hardly editable
1620 td->minWidths.fill(1);
1621
1622 td->maxWidths.resize(columns);
1623 td->maxWidths.fill(QFIXED_MAX);
1624
1625 // calculate minimum and maximum sizes of the columns
1626 for (int i = 0; i < columns; ++i) {
1627 for (int row = 0; row < rows; ++row) {
1628 const QTextTableCell cell = table->cellAt(row, i);
1629 const int cspan = cell.columnSpan();
1630
1631 if (cspan > 1 && i != cell.column())
1632 continue;
1633
1634 const QTextFormat fmt = cell.format();
1635 const QFixed leftPadding = td->leftPadding(fmt);
1636 const QFixed rightPadding = td->rightPadding(fmt);
1637 const QFixed widthPadding = leftPadding + rightPadding;
1638
1639 // to figure out the min and the max width lay out the cell at
1640 // maximum width. otherwise the maxwidth calculation sometimes
1641 // returns wrong values
1642 QTextLayoutStruct layoutStruct = layoutCell(table, cell, QFIXED_MAX, layoutFrom,
1643 layoutTo, td, absoluteTableY,
1644 /*withPageBreaks =*/false);
1645
1646 // distribute the minimum width over all columns the cell spans
1647 QFixed widthToDistribute = layoutStruct.minimumWidth + widthPadding;
1648 for (int n = 0; n < cspan; ++n) {
1649 const int col = i + n;
1650 QFixed w = widthToDistribute / (cspan - n);
1651 td->minWidths[col] = qMax(td->minWidths.at(col), w);
1652 widthToDistribute -= td->minWidths.at(col);
1653 if (widthToDistribute <= 0)
1654 break;
1655 }
1656
1657 QFixed maxW = td->maxWidths.at(i);
1658 if (layoutStruct.maximumWidth != QFIXED_MAX) {
1659 if (maxW == QFIXED_MAX)
1660 maxW = layoutStruct.maximumWidth + widthPadding;
1661 else
1662 maxW = qMax(maxW, layoutStruct.maximumWidth + widthPadding);
1663 }
1664 if (maxW == QFIXED_MAX)
1665 continue;
1666
1667 widthToDistribute = maxW;
1668 for (int n = 0; n < cspan; ++n) {
1669 const int col = i + n;
1670 QFixed w = widthToDistribute / (cspan - n);
1671 td->maxWidths[col] = qMax(td->minWidths.at(col), w);
1672 widthToDistribute -= td->maxWidths.at(col);
1673 if (widthToDistribute <= 0)
1674 break;
1675 }
1676 }
1677 }
1678
1679 // set fixed values, figure out total percentages used and number of
1680 // variable length cells. Also assign the minimum width for variable columns.
1681 QFixed totalPercentage;
1682 int variableCols = 0;
1683 QFixed totalMinWidth = 0;
1684 for (int i = 0; i < columns; ++i) {
1685 const QTextLength &length = columnWidthConstraints.at(i);
1686 if (length.type() == QTextLength::FixedLength) {
1687 td->minWidths[i] = td->widths[i] = qMax(scaleToDevice(QFixed::fromReal(length.rawValue())), td->minWidths.at(i));
1688 remainingWidth -= td->widths.at(i);
1689 } else if (length.type() == QTextLength::PercentageLength) {
1690 totalPercentage += QFixed::fromReal(length.rawValue());
1691 } else if (length.type() == QTextLength::VariableLength) {
1692 variableCols++;
1693
1694 td->widths[i] = td->minWidths.at(i);
1695 remainingWidth -= td->minWidths.at(i);
1696 }
1697 totalMinWidth += td->minWidths.at(i);
1698 }
1699
1700 // set percentage values
1701 {
1702 const QFixed totalPercentagedWidth = initialTotalWidth * totalPercentage / 100;
1703 QFixed remainingMinWidths = totalMinWidth;
1704 for (int i = 0; i < columns; ++i) {
1705 remainingMinWidths -= td->minWidths.at(i);
1706 if (columnWidthConstraints.at(i).type() == QTextLength::PercentageLength) {
1707 const QFixed allottedPercentage = QFixed::fromReal(columnWidthConstraints.at(i).rawValue());
1708
1709 const QFixed percentWidth = totalPercentagedWidth * allottedPercentage / totalPercentage;
1710 if (percentWidth >= td->minWidths.at(i)) {
1711 td->widths[i] = qBound(td->minWidths.at(i), percentWidth, remainingWidth - remainingMinWidths);
1712 } else {
1713 td->widths[i] = td->minWidths.at(i);
1714 }
1715 remainingWidth -= td->widths.at(i);
1716 }
1717 }
1718 }
1719
1720 // for variable columns distribute the remaining space
1721 if (variableCols > 0 && remainingWidth > 0) {
1722 QVarLengthArray<int> columnsWithProperMaxSize;
1723 for (int i = 0; i < columns; ++i)
1724 if (columnWidthConstraints.at(i).type() == QTextLength::VariableLength
1725 && td->maxWidths.at(i) != QFIXED_MAX)
1726 columnsWithProperMaxSize.append(i);
1727
1728 QFixed lastRemainingWidth = remainingWidth;
1729 while (remainingWidth > 0) {
1730 for (int k = 0; k < columnsWithProperMaxSize.count(); ++k) {
1731 const int col = columnsWithProperMaxSize[k];
1732 const int colsLeft = columnsWithProperMaxSize.count() - k;
1733 const QFixed w = qMin(td->maxWidths.at(col) - td->widths.at(col), remainingWidth / colsLeft);
1734 td->widths[col] += w;
1735 remainingWidth -= w;
1736 }
1737 if (remainingWidth == lastRemainingWidth)
1738 break;
1739 lastRemainingWidth = remainingWidth;
1740 }
1741
1742 if (remainingWidth > 0
1743 // don't unnecessarily grow variable length sized tables
1744 && fmt.width().type() != QTextLength::VariableLength) {
1745 const QFixed widthPerAnySizedCol = remainingWidth / variableCols;
1746 for (int col = 0; col < columns; ++col) {
1747 if (columnWidthConstraints.at(col).type() == QTextLength::VariableLength)
1748 td->widths[col] += widthPerAnySizedCol;
1749 }
1750 }
1751 }
1752
1753 td->columnPositions.resize(columns);
1754 td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border;
1755
1756 for (int i = 1; i < columns; ++i)
1757 td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->border + cellSpacing;
1758
1759 // - margin to compensate the + margin in columnPositions[0]
1760 const QFixed contentsWidth = td->columnPositions.last() + td->widths.last() + td->padding + td->border + cellSpacing - leftMargin;
1761
1762 // if the table is too big and causes an overflow re-do the layout with WrapAnywhere as wrap
1763 // mode
1764 if (docPrivate->defaultTextOption.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere
1765 && contentsWidth > td->contentsWidth) {
1766 docPrivate->defaultTextOption.setWrapMode(QTextOption::WrapAnywhere);
1767 // go back to the top of the function
1768 goto recalc_minmax_widths;
1769 }
1770
1771 td->contentsWidth = contentsWidth;
1772
1773 docPrivate->defaultTextOption.setWrapMode(oldDefaultWrapMode);
1774
1775 td->heights.resize(rows);
1776 td->heights.fill(0);
1777
1778 td->rowPositions.resize(rows);
1779 td->rowPositions[0] = topMargin /*includes table border*/ + cellSpacing + td->border;
1780
1781 bool haveRowSpannedCells = false;
1782
1783 // need to keep track of cell heights for vertical alignment
1784 QVector<QFixed> cellHeights;
1785 cellHeights.reserve(rows * columns);
1786
1787 QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
1788 if (pageHeight <= 0)
1789 pageHeight = QFIXED_MAX;
1790
1791 QVector<QFixed> heightToDistribute;
1792 heightToDistribute.resize(columns);
1793
1794 td->headerHeight = 0;
1795 const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1);
1796 const QFixed originalTopMargin = td->effectiveTopMargin;
1797 bool hasDroppedTable = false;
1798
1799 // now that we have the column widths we can lay out all cells with the right width.
1800 // spanning cells are only allowed to grow the last row spanned by the cell.
1801 //
1802 // ### this could be made faster by iterating over the cells array of QTextTable
1803 for (int r = 0; r < rows; ++r) {
1804 td->calcRowPosition(r);
1805
1806 const int tableStartPage = (absoluteTableY / pageHeight).truncate();
1807 const int currentPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
1808 const QFixed pageBottom = (currentPage + 1) * pageHeight - td->effectiveBottomMargin - absoluteTableY - cellSpacing - td->border;
1809 const QFixed pageTop = currentPage * pageHeight + td->effectiveTopMargin - absoluteTableY + cellSpacing + td->border;
1810 const QFixed nextPageTop = pageTop + pageHeight;
1811
1812 if (td->rowPositions[r] > pageBottom)
1813 td->rowPositions[r] = nextPageTop;
1814 else if (td->rowPositions[r] < pageTop)
1815 td->rowPositions[r] = pageTop;
1816
1817 bool dropRowToNextPage = true;
1818 int cellCountBeforeRow = cellHeights.size();
1819
1820 // if we drop the row to the next page we need to subtract the drop
1821 // distance from any row spanning cells
1822 QFixed dropDistance = 0;
1823
1824relayout:
1825 const int rowStartPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate();
1826 // if any of the header rows or the first non-header row start on the next page
1827 // then the entire header should be dropped
1828 if (r <= headerRowCount && rowStartPage > tableStartPage && !hasDroppedTable) {
1829 td->rowPositions[0] = nextPageTop;
1830 cellHeights.clear();
1831 td->effectiveTopMargin = originalTopMargin;
1832 hasDroppedTable = true;
1833 r = -1;
1834 continue;
1835 }
1836
1837 int rowCellCount = 0;
1838 for (int c = 0; c < columns; ++c) {
1839 QTextTableCell cell = table->cellAt(r, c);
1840 const int rspan = cell.rowSpan();
1841 const int cspan = cell.columnSpan();
1842
1843 if (cspan > 1 && cell.column() != c)
1844 continue;
1845
1846 if (rspan > 1) {
1847 haveRowSpannedCells = true;
1848
1849 const int cellRow = cell.row();
1850 if (cellRow != r) {
1851 // the last row gets all the remaining space
1852 if (cellRow + rspan - 1 == r)
1853 td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance);
1854 continue;
1855 }
1856 }
1857
1858 const QTextFormat fmt = cell.format();
1859
1860 const QFixed topPadding = td->topPadding(fmt);
1861 const QFixed bottomPadding = td->bottomPadding(fmt);
1862 const QFixed leftPadding = td->leftPadding(fmt);
1863 const QFixed rightPadding = td->rightPadding(fmt);
1864 const QFixed widthPadding = leftPadding + rightPadding;
1865
1866 ++rowCellCount;
1867
1868 const QFixed width = td->cellWidth(c, cspan) - widthPadding;
1869 QTextLayoutStruct layoutStruct = layoutCell(table, cell, width,
1870 layoutFrom, layoutTo,
1871 td, absoluteTableY,
1872 /*withPageBreaks =*/true);
1873
1874 const QFixed height = layoutStruct.y + bottomPadding + topPadding;
1875
1876 if (rspan > 1)
1877 heightToDistribute[c] = height + dropDistance;
1878 else
1879 td->heights[r] = qMax(td->heights.at(r), height);
1880
1881 cellHeights.append(layoutStruct.y);
1882
1883 QFixed childPos = td->rowPositions.at(r) + topPadding + flowPosition(cell.begin());
1884 if (childPos < pageBottom)
1885 dropRowToNextPage = false;
1886 }
1887
1888 if (rowCellCount > 0 && dropRowToNextPage) {
1889 dropDistance = nextPageTop - td->rowPositions[r];
1890 td->rowPositions[r] = nextPageTop;
1891 td->heights[r] = 0;
1892 dropRowToNextPage = false;
1893 cellHeights.resize(cellCountBeforeRow);
1894 if (r > headerRowCount)
1895 td->heights[r-1] = pageBottom - td->rowPositions[r-1];
1896 goto relayout;
1897 }
1898
1899 if (haveRowSpannedCells) {
1900 const QFixed effectiveHeight = td->heights.at(r) + td->border + cellSpacing + td->border;
1901 for (int c = 0; c < columns; ++c)
1902 heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0));
1903 }
1904
1905 if (r == headerRowCount - 1) {
1906 td->headerHeight = td->rowPositions[r] + td->heights[r] - td->rowPositions[0] + td->cellSpacing + 2 * td->border;
1907 td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate();
1908 td->effectiveTopMargin += td->headerHeight;
1909 }
1910 }
1911
1912 td->effectiveTopMargin = originalTopMargin;
1913
1914 // now that all cells have been properly laid out, we can compute the
1915 // vertical offsets for vertical alignment
1916 td->cellVerticalOffsets.resize(rows * columns);
1917 int cellIndex = 0;
1918 for (int r = 0; r < rows; ++r) {
1919 for (int c = 0; c < columns; ++c) {
1920 QTextTableCell cell = table->cellAt(r, c);
1921 if (cell.row() != r || cell.column() != c)
1922 continue;
1923
1924 const int rowSpan = cell.rowSpan();
1925 const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r);
1926
1927 const QTextCharFormat cellFormat = cell.format();
1928 const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(cellFormat) + td->bottomPadding(cellFormat);
1929
1930 QFixed offset = 0;
1931 switch (cellFormat.verticalAlignment()) {
1932 case QTextCharFormat::AlignMiddle:
1933 offset = (availableHeight - cellHeight) / 2;
1934 break;
1935 case QTextCharFormat::AlignBottom:
1936 offset = availableHeight - cellHeight;
1937 break;
1938 default:
1939 break;
1940 };
1941
1942 for (int rd = 0; rd < cell.rowSpan(); ++rd) {
1943 for (int cd = 0; cd < cell.columnSpan(); ++cd) {
1944 const int index = (c + cd) + (r + rd) * columns;
1945 td->cellVerticalOffsets[index] = offset;
1946 }
1947 }
1948 }
1949 }
1950
1951 td->minimumWidth = td->columnPositions.at(0);
1952 for (int i = 0; i < columns; ++i) {
1953 td->minimumWidth += td->minWidths.at(i) + 2 * td->border + cellSpacing;
1954 }
1955 td->minimumWidth += rightMargin - td->border;
1956
1957 td->maximumWidth = td->columnPositions.at(0);
1958 for (int i = 0; i < columns; ++i)
1959 if (td->maxWidths.at(i) != QFIXED_MAX)
1960 td->maximumWidth += td->maxWidths.at(i) + 2 * td->border + cellSpacing;
1961 td->maximumWidth += rightMargin - td->border;
1962
1963 td->updateTableSize();
1964 td->sizeDirty = false;
1965 return QRectF(); // invalid rect -> update everything
1966}
1967
1968void QTextDocumentLayoutPrivate::positionFloat(QTextFrame *frame, QTextLine *currentLine)
1969{
1970 QTextFrameData *fd = data(frame);
1971
1972 QTextFrame *parent = frame->parentFrame();
1973 Q_ASSERT(parent);
1974 QTextFrameData *pd = data(parent);
1975 Q_ASSERT(pd && pd->currentLayoutStruct);
1976
1977 QTextLayoutStruct *layoutStruct = pd->currentLayoutStruct;
1978
1979 if (!pd->floats.contains(frame))
1980 pd->floats.append(frame);
1981 fd->layoutDirty = true;
1982 Q_ASSERT(!fd->sizeDirty);
1983
1984// qDebug() << "positionFloat:" << frame << "width=" << fd->size.width;
1985 QFixed y = layoutStruct->y;
1986 if (currentLine) {
1987 QFixed left, right;
1988 floatMargins(y, layoutStruct, &left, &right);
1989// qDebug() << "have line: right=" << right << "left=" << left << "textWidth=" << currentLine->width();
1990 if (right - left < QFixed::fromReal(currentLine->naturalTextWidth()) + fd->size.width) {
1991 layoutStruct->pendingFloats.append(frame);
1992// qDebug() << " adding to pending list";
1993 return;
1994 }
1995 }
1996
1997 bool frameSpansIntoNextPage = (y + layoutStruct->frameY + fd->size.height > layoutStruct->pageBottom);
1998 if (frameSpansIntoNextPage && fd->size.height <= layoutStruct->pageHeight) {
1999 layoutStruct->newPage();
2000 y = layoutStruct->y;
2001
2002 frameSpansIntoNextPage = false;
2003 }
2004
2005 y = findY(y, layoutStruct, fd->size.width);
2006
2007 QFixed left, right;
2008 floatMargins(y, layoutStruct, &left, &right);
2009
2010 if (frame->frameFormat().position() == QTextFrameFormat::FloatLeft) {
2011 fd->position.x = left;
2012 fd->position.y = y;
2013 } else {
2014 fd->position.x = right - fd->size.width;
2015 fd->position.y = y;
2016 }
2017
2018 layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, fd->minimumWidth);
2019 layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, fd->maximumWidth);
2020
2021// qDebug()<< "float positioned at " << fd->position.x << fd->position.y;
2022 fd->layoutDirty = false;
2023
2024 // If the frame is a table, then positioning it will affect the size if it covers more than
2025 // one page, because of page breaks and repeating the header.
2026 if (qobject_cast<QTextTable *>(frame) != 0)
2027 fd->sizeDirty = frameSpansIntoNextPage;
2028}
2029
2030QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY)
2031{
2032 LDEBUG << "layoutFrame (pre)";
2033 Q_ASSERT(data(f)->sizeDirty);
2034// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2035
2036 QTextFrameFormat fformat = f->frameFormat();
2037
2038 QTextFrame *parent = f->parentFrame();
2039 const QTextFrameData *pd = parent ? data(parent) : 0;
2040
2041 const qreal maximumWidth = qMax(qreal(0), pd ? pd->contentsWidth.toReal() : document->pageSize().width());
2042 QFixed width = QFixed::fromReal(fformat.width().value(maximumWidth));
2043 if (fformat.width().type() == QTextLength::FixedLength)
2044 width = scaleToDevice(width);
2045
2046 const QFixed maximumHeight = pd ? pd->contentsHeight : -1;
2047 const QFixed height = (maximumHeight != -1 || fformat.height().type() != QTextLength::PercentageLength)
2048 ? QFixed::fromReal(fformat.height().value(maximumHeight.toReal()))
2049 : -1;
2050
2051 return layoutFrame(f, layoutFrom, layoutTo, width, height, parentY);
2052}
2053
2054QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY)
2055{
2056 LDEBUG << "layoutFrame from=" << layoutFrom << "to=" << layoutTo;
2057 Q_ASSERT(data(f)->sizeDirty);
2058// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame());
2059
2060 QTextFrameData *fd = data(f);
2061 QFixed newContentsWidth;
2062
2063 {
2064 QTextFrameFormat fformat = f->frameFormat();
2065 // set sizes of this frame from the format
2066 fd->topMargin = QFixed::fromReal(fformat.topMargin());
2067 fd->bottomMargin = QFixed::fromReal(fformat.bottomMargin());
2068 fd->leftMargin = QFixed::fromReal(fformat.leftMargin());
2069 fd->rightMargin = QFixed::fromReal(fformat.rightMargin());
2070 fd->border = QFixed::fromReal(fformat.border());
2071 fd->padding = QFixed::fromReal(fformat.padding());
2072
2073 QTextFrame *parent = f->parentFrame();
2074 const QTextFrameData *pd = parent ? data(parent) : 0;
2075
2076 // accumulate top and bottom margins
2077 if (parent) {
2078 fd->effectiveTopMargin = pd->effectiveTopMargin + fd->topMargin + fd->border + fd->padding;
2079 fd->effectiveBottomMargin = pd->effectiveBottomMargin + fd->topMargin + fd->border + fd->padding;
2080
2081 if (qobject_cast<QTextTable *>(parent)) {
2082 const QTextTableData *td = static_cast<const QTextTableData *>(pd);
2083 fd->effectiveTopMargin += td->cellSpacing + td->border + td->cellPadding;
2084 fd->effectiveBottomMargin += td->cellSpacing + td->border + td->cellPadding;
2085 }
2086 } else {
2087 fd->effectiveTopMargin = fd->topMargin + fd->border + fd->padding;
2088 fd->effectiveBottomMargin = fd->bottomMargin + fd->border + fd->padding;
2089 }
2090
2091 newContentsWidth = frameWidth - 2*(fd->border + fd->padding)
2092 - fd->leftMargin - fd->rightMargin;
2093
2094 if (frameHeight != -1) {
2095 fd->contentsHeight = frameHeight - 2*(fd->border + fd->padding)
2096 - fd->topMargin - fd->bottomMargin;
2097 } else {
2098 fd->contentsHeight = frameHeight;
2099 }
2100 }
2101
2102 if (isFrameFromInlineObject(f)) {
2103 // never reached, handled in resizeInlineObject/positionFloat instead
2104 return QRectF();
2105 }
2106
2107 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
2108 fd->contentsWidth = newContentsWidth;
2109 return layoutTable(table, layoutFrom, layoutTo, parentY);
2110 }
2111
2112 // set fd->contentsWidth temporarily, so that layoutFrame for the children
2113 // picks the right width. We'll initialize it properly at the end of this
2114 // function.
2115 fd->contentsWidth = newContentsWidth;
2116
2117 QTextLayoutStruct layoutStruct;
2118 layoutStruct.frame = f;
2119 layoutStruct.x_left = fd->leftMargin + fd->border + fd->padding;
2120 layoutStruct.x_right = layoutStruct.x_left + newContentsWidth;
2121 layoutStruct.y = fd->topMargin + fd->border + fd->padding;
2122 layoutStruct.frameY = parentY + fd->position.y;
2123 layoutStruct.contentsWidth = 0;
2124 layoutStruct.minimumWidth = 0;
2125 layoutStruct.maximumWidth = QFIXED_MAX;
2126 layoutStruct.fullLayout = fd->oldContentsWidth != newContentsWidth;
2127 layoutStruct.updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
2128 LDEBUG << "layoutStruct: x_left" << layoutStruct.x_left << "x_right" << layoutStruct.x_right
2129 << "fullLayout" << layoutStruct.fullLayout;
2130 fd->oldContentsWidth = newContentsWidth;
2131
2132 layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height());
2133 if (layoutStruct.pageHeight < 0)
2134 layoutStruct.pageHeight = QFIXED_MAX;
2135
2136 const int currentPage = layoutStruct.pageHeight == 0 ? 0 : (layoutStruct.frameY / layoutStruct.pageHeight).truncate();
2137 layoutStruct.pageTopMargin = fd->effectiveTopMargin;
2138 layoutStruct.pageBottomMargin = fd->effectiveBottomMargin;
2139 layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin;
2140
2141 if (!f->parentFrame())
2142 idealWidth = 0; // reset
2143
2144 QTextFrame::Iterator it = f->begin();
2145 layoutFlow(it, &layoutStruct, layoutFrom, layoutTo);
2146
2147 QFixed maxChildFrameWidth = 0;
2148 QList<QTextFrame *> children = f->childFrames();
2149 for (int i = 0; i < children.size(); ++i) {
2150 QTextFrame *c = children.at(i);
2151 QTextFrameData *cd = data(c);
2152 maxChildFrameWidth = qMax(maxChildFrameWidth, cd->size.width);
2153 }
2154
2155 const QFixed marginWidth = 2*(fd->border + fd->padding) + fd->leftMargin + fd->rightMargin;
2156 if (!f->parentFrame()) {
2157 idealWidth = qMax(maxChildFrameWidth, layoutStruct.contentsWidth).toReal();
2158 idealWidth += marginWidth.toReal();
2159 }
2160
2161 QFixed actualWidth = qMax(newContentsWidth, qMax(maxChildFrameWidth, layoutStruct.contentsWidth));
2162 fd->contentsWidth = actualWidth;
2163 if (newContentsWidth <= 0) { // nowrap layout?
2164 fd->contentsWidth = newContentsWidth;
2165 }
2166
2167 fd->minimumWidth = layoutStruct.minimumWidth;
2168 fd->maximumWidth = layoutStruct.maximumWidth;
2169
2170 fd->size.height = fd->contentsHeight == -1
2171 ? layoutStruct.y + fd->border + fd->padding + fd->bottomMargin
2172 : fd->contentsHeight + 2*(fd->border + fd->padding) + fd->topMargin + fd->bottomMargin;
2173 fd->size.width = actualWidth + marginWidth;
2174 fd->sizeDirty = false;
2175 if (layoutStruct.updateRectForFloats.isValid())
2176 layoutStruct.updateRect |= layoutStruct.updateRectForFloats;
2177 return layoutStruct.updateRect;
2178}
2179
2180void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct,
2181 int layoutFrom, int layoutTo, QFixed width)
2182{
2183 LDEBUG << "layoutFlow from=" << layoutFrom << "to=" << layoutTo;
2184 QTextFrameData *fd = data(layoutStruct->frame);
2185
2186 fd->currentLayoutStruct = layoutStruct;
2187
2188 QTextFrame::Iterator previousIt;
2189
2190 const bool inRootFrame = (it.parentFrame() == document->rootFrame());
2191 if (inRootFrame) {
2192 bool redoCheckPoints = layoutStruct->fullLayout || checkPoints.isEmpty();
2193
2194 if (!redoCheckPoints) {
2195 QVector<QCheckPoint>::Iterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), layoutFrom);
2196 if (checkPoint != checkPoints.end()) {
2197 if (checkPoint != checkPoints.begin())
2198 --checkPoint;
2199
2200 layoutStruct->y = checkPoint->y;
2201 layoutStruct->frameY = checkPoint->frameY;
2202 layoutStruct->minimumWidth = checkPoint->minimumWidth;
2203 layoutStruct->maximumWidth = checkPoint->maximumWidth;
2204 layoutStruct->contentsWidth = checkPoint->contentsWidth;
2205
2206 if (layoutStruct->pageHeight > 0) {
2207 int page = layoutStruct->currentPage();
2208 layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
2209 }
2210
2211 it = frameIteratorForTextPosition(checkPoint->positionInFrame);
2212 checkPoints.resize(checkPoint - checkPoints.begin() + 1);
2213
2214 if (checkPoint != checkPoints.begin()) {
2215 previousIt = it;
2216 --previousIt;
2217 }
2218 } else {
2219 redoCheckPoints = true;
2220 }
2221 }
2222
2223 if (redoCheckPoints) {
2224 checkPoints.clear();
2225 QCheckPoint cp;
2226 cp.y = layoutStruct->y;
2227 cp.frameY = layoutStruct->frameY;
2228 cp.positionInFrame = 0;
2229 cp.minimumWidth = layoutStruct->minimumWidth;
2230 cp.maximumWidth = layoutStruct->maximumWidth;
2231 cp.contentsWidth = layoutStruct->contentsWidth;
2232 checkPoints.append(cp);
2233 }
2234 }
2235
2236 QTextBlockFormat previousBlockFormat = previousIt.currentBlock().blockFormat();
2237
2238 QFixed maximumBlockWidth = 0;
2239 while (!it.atEnd()) {
2240 QTextFrame *c = it.currentFrame();
2241
2242 int docPos;
2243 if (it.currentFrame())
2244 docPos = it.currentFrame()->firstPosition();
2245 else
2246 docPos = it.currentBlock().position();
2247
2248 if (inRootFrame) {
2249 if (qAbs(layoutStruct->y - checkPoints.last().y) > 2000) {
2250 QFixed left, right;
2251 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2252 if (left == layoutStruct->x_left && right == layoutStruct->x_right) {
2253 QCheckPoint p;
2254 p.y = layoutStruct->y;
2255 p.frameY = layoutStruct->frameY;
2256 p.positionInFrame = docPos;
2257 p.minimumWidth = layoutStruct->minimumWidth;
2258 p.maximumWidth = layoutStruct->maximumWidth;
2259 p.contentsWidth = layoutStruct->contentsWidth;
2260 checkPoints.append(p);
2261
2262 if (currentLazyLayoutPosition != -1
2263 && docPos > currentLazyLayoutPosition + lazyLayoutStepSize)
2264 break;
2265
2266 }
2267 }
2268 }
2269
2270 if (c) {
2271 // position child frame
2272 QTextFrameData *cd = data(c);
2273
2274 QTextFrameFormat fformat = c->frameFormat();
2275
2276 if (fformat.position() == QTextFrameFormat::InFlow) {
2277 if (fformat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
2278 layoutStruct->newPage();
2279
2280 QFixed left, right;
2281 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2282 left = qMax(left, layoutStruct->x_left);
2283 right = qMin(right, layoutStruct->x_right);
2284
2285 if (right - left < cd->size.width) {
2286 layoutStruct->y = findY(layoutStruct->y, layoutStruct, cd->size.width);
2287 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2288 }
2289
2290 QFixedPoint pos(left, layoutStruct->y);
2291
2292 Qt::Alignment align = Qt::AlignLeft;
2293
2294 QTextTable *table = qobject_cast<QTextTable *>(c);
2295
2296 if (table)
2297 align = table->format().alignment() & Qt::AlignHorizontal_Mask;
2298
2299 // detect whether we have any alignment in the document that disallows optimizations,
2300 // such as not laying out the document again in a textedit with wrapping disabled.
2301 if (inRootFrame && !(align & Qt::AlignLeft))
2302 contentHasAlignment = true;
2303
2304 cd->position = pos;
2305
2306 if (document->pageSize().height() > 0.0f)
2307 cd->sizeDirty = true;
2308
2309 if (cd->sizeDirty) {
2310 if (width != 0)
2311 layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
2312 else
2313 layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
2314
2315 QFixed absoluteChildPos = table ? pos.y + static_cast<QTextTableData *>(data(table))->rowPositions.at(0) : pos.y + firstChildPos(c);
2316 absoluteChildPos += layoutStruct->frameY;
2317
2318 // drop entire frame to next page if first child of frame is on next page
2319 if (absoluteChildPos > layoutStruct->pageBottom) {
2320 layoutStruct->newPage();
2321 pos.y = layoutStruct->y;
2322
2323 cd->position = pos;
2324 cd->sizeDirty = true;
2325
2326 if (width != 0)
2327 layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY);
2328 else
2329 layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY);
2330 }
2331 }
2332
2333 // align only if there is space for alignment
2334 if (right - left > cd->size.width) {
2335 if (align & Qt::AlignRight)
2336 pos.x += layoutStruct->x_right - cd->size.width;
2337 else if (align & Qt::AlignHCenter)
2338 pos.x += (layoutStruct->x_right - cd->size.width) / 2;
2339 }
2340
2341 cd->position = pos;
2342
2343 layoutStruct->y += cd->size.height;
2344 const int page = layoutStruct->currentPage();
2345 layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin;
2346
2347 cd->layoutDirty = false;
2348
2349 if (c->frameFormat().pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
2350 layoutStruct->newPage();
2351 } else {
2352 QRectF oldFrameRect(cd->position.toPointF(), cd->size.toSizeF());
2353 QRectF updateRect;
2354
2355 if (cd->sizeDirty)
2356 updateRect = layoutFrame(c, layoutFrom, layoutTo);
2357
2358 positionFloat(c);
2359
2360 // If the size was made dirty when the position was set, layout again
2361 if (cd->sizeDirty)
2362 updateRect = layoutFrame(c, layoutFrom, layoutTo);
2363
2364 QRectF frameRect(cd->position.toPointF(), cd->size.toSizeF());
2365
2366 if (frameRect == oldFrameRect && updateRect.isValid())
2367 updateRect.translate(cd->position.toPointF());
2368 else
2369 updateRect = frameRect;
2370
2371 layoutStruct->addUpdateRectForFloat(updateRect);
2372 if (oldFrameRect.isValid())
2373 layoutStruct->addUpdateRectForFloat(oldFrameRect);
2374 }
2375
2376 layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, cd->minimumWidth);
2377 layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, cd->maximumWidth);
2378
2379 previousIt = it;
2380 ++it;
2381 } else {
2382 QTextFrame::Iterator lastIt;
2383 if (!previousIt.atEnd())
2384 lastIt = previousIt;
2385 previousIt = it;
2386 QTextBlock block = it.currentBlock();
2387 ++it;
2388
2389 const QTextBlockFormat blockFormat = block.blockFormat();
2390
2391 if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
2392 layoutStruct->newPage();
2393
2394 const QFixed origY = layoutStruct->y;
2395 const QFixed origPageBottom = layoutStruct->pageBottom;
2396 const QFixed origMaximumWidth = layoutStruct->maximumWidth;
2397 layoutStruct->maximumWidth = 0;
2398
2399 const QTextBlockFormat *previousBlockFormatPtr = 0;
2400 if (lastIt.currentBlock().isValid())
2401 previousBlockFormatPtr = &previousBlockFormat;
2402
2403 // layout and position child block
2404 layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
2405
2406 // detect whether we have any alignment in the document that disallows optimizations,
2407 // such as not laying out the document again in a textedit with wrapping disabled.
2408 if (inRootFrame && !(block.layout()->textOption().alignment() & Qt::AlignLeft))
2409 contentHasAlignment = true;
2410
2411 // if the block right before a table is empty 'hide' it by
2412 // positioning it into the table border
2413 if (isEmptyBlockBeforeTable(block, blockFormat, it)) {
2414 const QTextBlock lastBlock = lastIt.currentBlock();
2415 const qreal lastBlockBottomMargin = lastBlock.isValid() ? lastBlock.blockFormat().bottomMargin() : 0.0f;
2416 layoutStruct->y = origY + QFixed::fromReal(qMax(lastBlockBottomMargin, block.blockFormat().topMargin()));
2417 layoutStruct->pageBottom = origPageBottom;
2418 } else {
2419 // if the block right after a table is empty then 'hide' it, too
2420 if (isEmptyBlockAfterTable(block, lastIt.currentFrame())) {
2421 QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
2422 QTextLayout *layout = block.layout();
2423
2424 QPointF pos((td->position.x + td->size.width).toReal(),
2425 (td->position.y + td->size.height).toReal() - layout->boundingRect().height());
2426
2427 layout->setPosition(pos);
2428 layoutStruct->y = origY;
2429 layoutStruct->pageBottom = origPageBottom;
2430 }
2431
2432 // if the block right after a table starts with a line separator, shift it up by one line
2433 if (isLineSeparatorBlockAfterTable(block, lastIt.currentFrame())) {
2434 QTextTableData *td = static_cast<QTextTableData *>(data(lastIt.currentFrame()));
2435 QTextLayout *layout = block.layout();
2436
2437 QFixed height = QFixed::fromReal(layout->lineAt(0).height());
2438
2439 if (layoutStruct->pageBottom == origPageBottom) {
2440 layoutStruct->y -= height;
2441 layout->setPosition(layout->position() - QPointF(0, height.toReal()));
2442 } else {
2443 // relayout block to correctly handle page breaks
2444 layoutStruct->y = origY - height;
2445 layoutStruct->pageBottom = origPageBottom;
2446 layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr);
2447 }
2448
2449 QPointF linePos((td->position.x + td->size.width).toReal(),
2450 (td->position.y + td->size.height - height).toReal());
2451
2452 layout->lineAt(0).setPosition(linePos - layout->position());
2453 }
2454
2455 if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
2456 layoutStruct->newPage();
2457 }
2458
2459 maximumBlockWidth = qMax(maximumBlockWidth, layoutStruct->maximumWidth);
2460 layoutStruct->maximumWidth = origMaximumWidth;
2461 previousBlockFormat = blockFormat;
2462 }
2463 }
2464 if (layoutStruct->maximumWidth == QFIXED_MAX && maximumBlockWidth > 0)
2465 layoutStruct->maximumWidth = maximumBlockWidth;
2466 else
2467 layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maximumBlockWidth);
2468
2469 // a float at the bottom of a frame may make it taller, hence the qMax() for layoutStruct->y.
2470 // we don't need to do it for tables though because floats in tables are per table
2471 // and not per cell and layoutCell already takes care of doing the same as we do here
2472 if (!qobject_cast<QTextTable *>(layoutStruct->frame)) {
2473 QList<QTextFrame *> children = layoutStruct->frame->childFrames();
2474 for (int i = 0; i < children.count(); ++i) {
2475 QTextFrameData *fd = data(children.at(i));
2476 if (!fd->layoutDirty && children.at(i)->frameFormat().position() != QTextFrameFormat::InFlow)
2477 layoutStruct->y = qMax(layoutStruct->y, fd->position.y + fd->size.height);
2478 }
2479 }
2480
2481 if (inRootFrame) {
2482 // we assume that any float is aligned in a way that disallows the optimizations that rely
2483 // on unaligned content.
2484 if (!fd->floats.isEmpty())
2485 contentHasAlignment = true;
2486
2487 if (it.atEnd()) {
2488 //qDebug() << "layout done!";
2489 currentLazyLayoutPosition = -1;
2490 QCheckPoint cp;
2491 cp.y = layoutStruct->y;
2492 cp.positionInFrame = docPrivate->length();
2493 cp.minimumWidth = layoutStruct->minimumWidth;
2494 cp.maximumWidth = layoutStruct->maximumWidth;
2495 cp.contentsWidth = layoutStruct->contentsWidth;
2496 checkPoints.append(cp);
2497 checkPoints.reserve(checkPoints.size());
2498 } else {
2499 currentLazyLayoutPosition = checkPoints.last().positionInFrame;
2500 // #######
2501 //checkPoints.last().positionInFrame = q->document()->docHandle()->length();
2502 }
2503 }
2504
2505
2506 fd->currentLayoutStruct = 0;
2507}
2508
2509void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
2510 QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat)
2511{
2512 Q_Q(QTextDocumentLayout);
2513
2514 QTextLayout *tl = bl.layout();
2515 const int blockLength = bl.length();
2516
2517 LDEBUG << "layoutBlock from=" << layoutFrom << "to=" << layoutTo;
2518
2519// qDebug() << "layoutBlock; width" << layoutStruct->x_right - layoutStruct->x_left << "(maxWidth is btw" << tl->maximumWidth() << ')';
2520
2521 if (previousBlockFormat) {
2522 qreal margin = qMax(blockFormat.topMargin(), previousBlockFormat->bottomMargin());
2523 if (margin > 0 && q->paintDevice()) {
2524 margin *= qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi());
2525 }
2526 layoutStruct->y += QFixed::fromReal(margin);
2527 }
2528
2529 //QTextFrameData *fd = data(layoutStruct->frame);
2530
2531 Qt::LayoutDirection dir = bl.textDirection();
2532
2533 QFixed extraMargin;
2534 if (docPrivate->defaultTextOption.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) {
2535 QFontMetricsF fm(bl.charFormat().font());
2536 extraMargin = QFixed::fromReal(fm.width(QChar(QChar(0x21B5))));
2537 }
2538
2539 const QFixed indent = this->blockIndent(blockFormat);
2540 const QFixed totalLeftMargin = QFixed::fromReal(blockFormat.leftMargin()) + (dir == Qt::RightToLeft ? extraMargin : indent);
2541 const QFixed totalRightMargin = QFixed::fromReal(blockFormat.rightMargin()) + (dir == Qt::RightToLeft ? indent : extraMargin);
2542
2543 const QPointF oldPosition = tl->position();
2544 tl->setPosition(QPointF(layoutStruct->x_left.toReal(), layoutStruct->y.toReal()));
2545
2546 if (layoutStruct->fullLayout
2547 || (blockPosition + blockLength > layoutFrom && blockPosition <= layoutTo)
2548 // force relayout if we cross a page boundary
2549 || (layoutStruct->pageHeight != QFIXED_MAX && layoutStruct->absoluteY() + QFixed::fromReal(tl->boundingRect().height()) > layoutStruct->pageBottom)) {
2550
2551 LDEBUG << " do layout";
2552 QTextOption option = docPrivate->defaultTextOption;
2553 option.setTextDirection(dir);
2554 option.setTabs( blockFormat.tabPositions() );
2555
2556 Qt::Alignment align = docPrivate->defaultTextOption.alignment();
2557 if (blockFormat.hasProperty(QTextFormat::BlockAlignment))
2558 align = blockFormat.alignment();
2559 option.setAlignment(QStyle::visualAlignment(dir, align)); // for paragraph that are RTL, alignment is auto-reversed;
2560
2561 if (blockFormat.nonBreakableLines() || document->pageSize().width() < 0) {
2562 option.setWrapMode(QTextOption::ManualWrap);
2563 }
2564
2565 tl->setTextOption(option);
2566
2567 const bool haveWordOrAnyWrapMode = (option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere);
2568
2569// qDebug() << " layouting block at" << bl.position();
2570 const QFixed cy = layoutStruct->y;
2571 const QFixed l = layoutStruct->x_left + totalLeftMargin;
2572 const QFixed r = layoutStruct->x_right - totalRightMargin;
2573
2574 tl->beginLayout();
2575 bool firstLine = true;
2576 while (1) {
2577 QTextLine line = tl->createLine();
2578 if (!line.isValid())
2579 break;
2580 line.setLeadingIncluded(true);
2581
2582 QFixed left, right;
2583 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2584 left = qMax(left, l);
2585 right = qMin(right, r);
2586 QFixed text_indent;
2587 if (firstLine) {
2588 text_indent = QFixed::fromReal(blockFormat.textIndent());
2589 if (dir == Qt::LeftToRight)
2590 left += text_indent;
2591 else
2592 right -= text_indent;
2593 firstLine = false;
2594 }
2595// qDebug() << "layout line y=" << currentYPos << "left=" << left << "right=" <<right;
2596
2597 if (fixedColumnWidth != -1)
2598 line.setNumColumns(fixedColumnWidth, (right - left).toReal());
2599 else
2600 line.setLineWidth((right - left).toReal());
2601
2602// qDebug() << "layoutBlock; layouting line with width" << right - left << "->textWidth" << line.textWidth();
2603 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2604 left = qMax(left, l);
2605 right = qMin(right, r);
2606 if (dir == Qt::LeftToRight)
2607 left += text_indent;
2608 else
2609 right -= text_indent;
2610
2611 if (fixedColumnWidth == -1 && QFixed::fromReal(line.naturalTextWidth()) > right-left) {
2612 // float has been added in the meantime, redo
2613 layoutStruct->pendingFloats.clear();
2614
2615 line.setLineWidth((right-left).toReal());
2616 if (QFixed::fromReal(line.naturalTextWidth()) > right-left) {
2617 if (haveWordOrAnyWrapMode) {
2618 option.setWrapMode(QTextOption::WrapAnywhere);
2619 tl->setTextOption(option);
2620 }
2621
2622 layoutStruct->pendingFloats.clear();
2623 // lines min width more than what we have
2624 layoutStruct->y = findY(layoutStruct->y, layoutStruct, QFixed::fromReal(line.naturalTextWidth()));
2625 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2626 left = qMax(left, l);
2627 right = qMin(right, r);
2628 if (dir == Qt::LeftToRight)
2629 left += text_indent;
2630 else
2631 right -= text_indent;
2632 line.setLineWidth(qMax<qreal>(line.naturalTextWidth(), (right-left).toReal()));
2633
2634 if (haveWordOrAnyWrapMode) {
2635 option.setWrapMode(QTextOption::WordWrap);
2636 tl->setTextOption(option);
2637 }
2638 }
2639
2640 }
2641
2642 QFixed lineHeight = QFixed::fromReal(line.height());
2643 if (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineHeight > layoutStruct->pageBottom) {
2644 layoutStruct->newPage();
2645
2646 floatMargins(layoutStruct->y, layoutStruct, &left, &right);
2647 left = qMax(left, l);
2648 right = qMin(right, r);
2649 if (dir == Qt::LeftToRight)
2650 left += text_indent;
2651 else
2652 right -= text_indent;
2653 }
2654
2655 line.setPosition(QPointF((left - layoutStruct->x_left).toReal(), (layoutStruct->y - cy).toReal()));
2656 layoutStruct->y += lineHeight;
2657 layoutStruct->contentsWidth
2658 = qMax<QFixed>(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + line.naturalTextWidth()) + totalRightMargin);
2659
2660 // position floats
2661 for (int i = 0; i < layoutStruct->pendingFloats.size(); ++i) {
2662 QTextFrame *f = layoutStruct->pendingFloats.at(i);
2663 positionFloat(f);
2664 }
2665 layoutStruct->pendingFloats.clear();
2666 }
2667 tl->endLayout();
2668 } else {
2669 const int cnt = tl->lineCount();
2670 for (int i = 0; i < cnt; ++i) {
2671 LDEBUG << "going to move text line" << i;
2672 QTextLine line = tl->lineAt(i);
2673 layoutStruct->contentsWidth
2674 = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + tl->lineAt(i).naturalTextWidth()) + totalRightMargin);
2675 const QFixed lineHeight = QFixed::fromReal(line.height());
2676 if (layoutStruct->pageHeight != QFIXED_MAX) {
2677 if (layoutStruct->absoluteY() + lineHeight > layoutStruct->pageBottom)
2678 layoutStruct->newPage();
2679 line.setPosition(QPointF(line.position().x(), layoutStruct->y.toReal() - tl->position().y()));
2680 }
2681 layoutStruct->y += lineHeight;
2682 }
2683 if (layoutStruct->updateRect.isValid()
2684 && blockLength > 1) {
2685 if (layoutFrom >= blockPosition + blockLength) {
2686 // if our height didn't change and the change in the document is
2687 // in one of the later paragraphs, then we don't need to repaint
2688 // this one
2689 layoutStruct->updateRect.setTop(qMax(layoutStruct->updateRect.top(), layoutStruct->y.toReal()));
2690 } else if (layoutTo < blockPosition) {
2691 if (oldPosition == tl->position())
2692 // if the change in the document happened earlier in the document
2693 // and our position did /not/ change because none of the earlier paragraphs
2694 // or frames changed their height, then we don't need to repaint
2695 // this one
2696 layoutStruct->updateRect.setBottom(qMin(layoutStruct->updateRect.bottom(), tl->position().y()));
2697 else
2698 layoutStruct->updateRect.setBottom(qreal(INT_MAX)); // reset
2699 }
2700 }
2701 }
2702
2703 // ### doesn't take floats into account. would need to do it per line. but how to retrieve then? (Simon)
2704 const QFixed margins = totalLeftMargin + totalRightMargin;
2705 layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, QFixed::fromReal(tl->minimumWidth()) + margins);
2706
2707 const QFixed maxW = QFixed::fromReal(tl->maximumWidth()) + margins;
2708
2709 if (maxW > 0) {
2710 if (layoutStruct->maximumWidth == QFIXED_MAX)
2711 layoutStruct->maximumWidth = maxW;
2712 else
2713 layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maxW);
2714 }
2715}
2716
2717void QTextDocumentLayoutPrivate::floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct,
2718 QFixed *left, QFixed *right) const
2719{
2720// qDebug() << "floatMargins y=" << y;
2721 *left = layoutStruct->x_left;
2722 *right = layoutStruct->x_right;
2723 QTextFrameData *lfd = data(layoutStruct->frame);
2724 for (int i = 0; i < lfd->floats.size(); ++i) {
2725 QTextFrameData *fd = data(lfd->floats.at(i));
2726 if (!fd->layoutDirty) {
2727 if (fd->position.y <= y && fd->position.y + fd->size.height > y) {
2728// qDebug() << "adjusting with float" << f << fd->position.x()<< fd->size.width();
2729 if (lfd->floats.at(i)->frameFormat().position() == QTextFrameFormat::FloatLeft)
2730 *left = qMax(*left, fd->position.x + fd->size.width);
2731 else
2732 *right = qMin(*right, fd->position.x);
2733 }
2734 }
2735 }
2736// qDebug() << "floatMargins: left="<<*left<<"right="<<*right<<"y="<<y;
2737}
2738
2739QFixed QTextDocumentLayoutPrivate::findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const
2740{
2741 QFixed right, left;
2742 requiredWidth = qMin(requiredWidth, layoutStruct->x_right - layoutStruct->x_left);
2743
2744// qDebug() << "findY:" << yFrom;
2745 while (1) {
2746 floatMargins(yFrom, layoutStruct, &left, &right);
2747// qDebug() << " yFrom=" << yFrom<<"right=" << right << "left=" << left << "requiredWidth=" << requiredWidth;
2748 if (right-left >= requiredWidth)
2749 break;
2750
2751 // move float down until we find enough space
2752 QFixed newY = QFIXED_MAX;
2753 QTextFrameData *lfd = data(layoutStruct->frame);
2754 for (int i = 0; i < lfd->floats.size(); ++i) {
2755 QTextFrameData *fd = data(lfd->floats.at(i));
2756 if (!fd->layoutDirty) {
2757 if (fd->position.y <= yFrom && fd->position.y + fd->size.height > yFrom)
2758 newY = qMin(newY, fd->position.y + fd->size.height);
2759 }
2760 }
2761 if (newY == QFIXED_MAX)
2762 break;
2763 yFrom = newY;
2764 }
2765 return yFrom;
2766}
2767
2768QTextDocumentLayout::QTextDocumentLayout(QTextDocument *doc)
2769 : QAbstractTextDocumentLayout(*new QTextDocumentLayoutPrivate, doc)
2770{
2771 registerHandler(QTextFormat::ImageObject, new QTextImageHandler(this));
2772}
2773
2774
2775void QTextDocumentLayout::draw(QPainter *painter, const PaintContext &context)
2776{
2777 Q_D(QTextDocumentLayout);
2778 QTextFrame *frame = d->document->rootFrame();
2779 QTextFrameData *fd = data(frame);
2780
2781 if(fd->sizeDirty)
2782 return;
2783
2784 if (context.clip.isValid()) {
2785 d->ensureLayouted(QFixed::fromReal(context.clip.bottom()));
2786 } else {
2787 d->ensureLayoutFinished();
2788 }
2789
2790 QFixed width = fd->size.width;
2791 if (d->document->pageSize().width() == 0 && d->viewportRect.isValid()) {
2792 // we're in NoWrap mode, meaning the frame should expand to the viewport
2793 // so that backgrounds are drawn correctly
2794 fd->size.width = qMax(width, QFixed::fromReal(d->viewportRect.right()));
2795 }
2796
2797 // Make sure we conform to the root frames bounds when drawing.
2798 d->clipRect = QRectF(fd->position.toPointF(), fd->size.toSizeF()).adjusted(fd->leftMargin.toReal(), 0, -fd->rightMargin.toReal(), 0);
2799 d->drawFrame(QPointF(), painter, context, frame);
2800 fd->size.width = width;
2801}
2802
2803void QTextDocumentLayout::setViewport(const QRectF &viewport)
2804{
2805 Q_D(QTextDocumentLayout);
2806 d->viewportRect = viewport;
2807}
2808
2809static void markFrames(QTextFrame *current, int from, int oldLength, int length)
2810{
2811 int end = qMax(oldLength, length) + from;
2812
2813 if (current->firstPosition() >= end || current->lastPosition() < from)
2814 return;
2815
2816 QTextFrameData *fd = data(current);
2817 for (int i = 0; i < fd->floats.size(); ++i) {
2818 QTextFrame *f = fd->floats[i];
2819 if (!f) {
2820 // float got removed in editing operation
2821 fd->floats.removeAt(i);
2822 --i;
2823 }
2824 }
2825
2826 fd->layoutDirty = true;
2827 fd->sizeDirty = true;
2828
2829// qDebug(" marking frame (%d--%d) as dirty", current->firstPosition(), current->lastPosition());
2830 QList<QTextFrame *> children = current->childFrames();
2831 for (int i = 0; i < children.size(); ++i)
2832 markFrames(children.at(i), from, oldLength, length);
2833}
2834
2835void QTextDocumentLayout::documentChanged(int from, int oldLength, int length)
2836{
2837 Q_D(QTextDocumentLayout);
2838
2839 QTextBlock blockIt = document()->findBlock(from);
2840 QTextBlock endIt = document()->findBlock(qMax(0, from + length - 1));
2841 if (endIt.isValid())
2842 endIt = endIt.next();
2843 for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next())
2844 blockIt.clearLayout();
2845
2846 if (d->docPrivate->pageSize.isNull())
2847 return;
2848
2849 QRectF updateRect;
2850
2851 d->lazyLayoutStepSize = 1000;
2852 d->sizeChangedTimer.stop();
2853 d->insideDocumentChange = true;
2854
2855 const int documentLength = d->docPrivate->length();
2856 const bool fullLayout = (oldLength == 0 && length == documentLength);
2857 const bool smallChange = documentLength > 0
2858 && (qMax(length, oldLength) * 100 / documentLength) < 5;
2859
2860 // don't show incremental layout progress (avoid scroll bar flicker)
2861 // if we see only a small change in the document and we're either starting
2862 // a layout run or we're already in progress for that and we haven't seen
2863 // any bigger change previously (showLayoutProgress already false)
2864 if (smallChange
2865 && (d->currentLazyLayoutPosition == -1 || d->showLayoutProgress == false))
2866 d->showLayoutProgress = false;
2867 else
2868 d->showLayoutProgress = true;
2869
2870 if (fullLayout) {
2871 d->contentHasAlignment = false;
2872 d->currentLazyLayoutPosition = 0;
2873 d->checkPoints.clear();
2874 d->layoutStep();
2875 } else {
2876 d->ensureLayoutedByPosition(from);
2877 updateRect = doLayout(from, oldLength, length);
2878 }
2879
2880 if (!d->layoutTimer.isActive() && d->currentLazyLayoutPosition != -1)
2881 d->layoutTimer.start(10, this);
2882
2883 d->insideDocumentChange = false;
2884
2885 if (d->showLayoutProgress) {
2886 const QSizeF newSize = dynamicDocumentSize();
2887 if (newSize != d->lastReportedSize) {
2888 d->lastReportedSize = newSize;
2889 emit documentSizeChanged(newSize);
2890 }
2891 }
2892
2893 if (!updateRect.isValid()) {
2894 // don't use the frame size, it might have shrunken
2895 updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX)));
2896 }
2897
2898 emit update(updateRect);
2899}
2900
2901QRectF QTextDocumentLayout::doLayout(int from, int oldLength, int length)
2902{
2903 Q_D(QTextDocumentLayout);
2904
2905// qDebug("documentChange: from=%d, oldLength=%d, length=%d", from, oldLength, length);
2906
2907 // mark all frames between f_start and f_end as dirty
2908 markFrames(d->docPrivate->rootFrame(), from, oldLength, length);
2909
2910 QRectF updateRect;
2911
2912 QTextFrame *root = d->docPrivate->rootFrame();
2913 if(data(root)->sizeDirty)
2914 updateRect = d->layoutFrame(root, from, from + length);
2915 data(root)->layoutDirty = false;
2916
2917 if (d->currentLazyLayoutPosition == -1)
2918 layoutFinished();
2919 else if (d->showLayoutProgress)
2920 d->sizeChangedTimer.start(0, this);
2921
2922 return updateRect;
2923}
2924
2925int QTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
2926{
2927 Q_D(const QTextDocumentLayout);
2928 d->ensureLayouted(QFixed::fromReal(point.y()));
2929 QTextFrame *f = d->docPrivate->rootFrame();
2930 int position = 0;
2931 QTextLayout *l = 0;
2932 QFixedPoint pointf;
2933 pointf.x = QFixed::fromReal(point.x());
2934 pointf.y = QFixed::fromReal(point.y());
2935 QTextDocumentLayoutPrivate::HitPoint p = d->hitTest(f, pointf, &position, &l, accuracy);
2936 if (accuracy == Qt::ExactHit && p < QTextDocumentLayoutPrivate::PointExact)
2937 return -1;
2938
2939 // ensure we stay within document bounds
2940 int lastPos = f->lastPosition();
2941 if (l && !l->preeditAreaText().isEmpty())
2942 lastPos += l->preeditAreaText().length();
2943 if (position > lastPos)
2944 position = lastPos;
2945 else if (position < 0)
2946 position = 0;
2947
2948 return position;
2949}
2950
2951void QTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
2952{
2953 Q_D(QTextDocumentLayout);
2954 QTextCharFormat f = format.toCharFormat();
2955 Q_ASSERT(f.isValid());
2956 QTextObjectHandler handler = d->handlers.value(f.objectType());
2957 if (!handler.component)
2958 return;
2959
2960 QSizeF intrinsic = handler.iface->intrinsicSize(d->document, posInDocument, format);
2961
2962 QTextFrameFormat::Position pos = QTextFrameFormat::InFlow;
2963 QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
2964 if (frame) {
2965 pos = frame->frameFormat().position();
2966 QTextFrameData *fd = data(frame);
2967 fd->sizeDirty = false;
2968 fd->size = QFixedSize::fromSizeF(intrinsic);
2969 fd->minimumWidth = fd->maximumWidth = fd->size.width;
2970 }
2971
2972 QSizeF inlineSize = (pos == QTextFrameFormat::InFlow ? intrinsic : QSizeF(0, 0));
2973 item.setWidth(inlineSize.width());
2974 if (f.verticalAlignment() == QTextCharFormat::AlignMiddle) {
2975 item.setDescent(inlineSize.height() / 2);
2976 item.setAscent(inlineSize.height() / 2 - 1);
2977 } else {
2978 item.setDescent(0);
2979 item.setAscent(inlineSize.height() - 1);
2980 }
2981}
2982
2983void QTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format)
2984{
2985 Q_D(QTextDocumentLayout);
2986 Q_UNUSED(posInDocument);
2987 if (item.width() != 0)
2988 // inline
2989 return;
2990
2991 QTextCharFormat f = format.toCharFormat();
2992 Q_ASSERT(f.isValid());
2993 QTextObjectHandler handler = d->handlers.value(f.objectType());
2994 if (!handler.component)
2995 return;
2996
2997 QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
2998 if (!frame)
2999 return;
3000
3001 QTextBlock b = d->document->findBlock(frame->firstPosition());
3002 QTextLine line;
3003 if (b.position() <= frame->firstPosition() && b.position() + b.length() > frame->lastPosition())
3004 line = b.layout()->lineAt(b.layout()->lineCount()-1);
3005// qDebug() << "layoutObject: line.isValid" << line.isValid() << b.position() << b.length() <<
3006// frame->firstPosition() << frame->lastPosition();
3007 d->positionFloat(frame, line.isValid() ? &line : 0);
3008}
3009
3010void QTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item,
3011 int posInDocument, const QTextFormat &format)
3012{
3013 Q_D(QTextDocumentLayout);
3014 QTextCharFormat f = format.toCharFormat();
3015 Q_ASSERT(f.isValid());
3016 QTextFrame *frame = qobject_cast<QTextFrame *>(d->document->objectForFormat(f));
3017 if (frame && frame->frameFormat().position() != QTextFrameFormat::InFlow)
3018 return; // don't draw floating frames from inline objects here but in drawFlow instead
3019
3020// qDebug() << "drawObject at" << r;
3021 QAbstractTextDocumentLayout::drawInlineObject(p, rect, item, posInDocument, format);
3022}
3023
3024int QTextDocumentLayout::dynamicPageCount() const
3025{
3026 Q_D(const QTextDocumentLayout);
3027 const QSizeF pgSize = d->document->pageSize();
3028 if (pgSize.height() < 0)
3029 return 1;
3030 return qCeil(dynamicDocumentSize().height() / pgSize.height());
3031}
3032
3033QSizeF QTextDocumentLayout::dynamicDocumentSize() const
3034{
3035 Q_D(const QTextDocumentLayout);
3036 return data(d->docPrivate->rootFrame())->size.toSizeF();
3037}
3038
3039int QTextDocumentLayout::pageCount() const
3040{
3041 Q_D(const QTextDocumentLayout);
3042 d->ensureLayoutFinished();
3043 return dynamicPageCount();
3044}
3045
3046QSizeF QTextDocumentLayout::documentSize() const
3047{
3048 Q_D(const QTextDocumentLayout);
3049 d->ensureLayoutFinished();
3050 return dynamicDocumentSize();
3051}
3052
3053void QTextDocumentLayoutPrivate::ensureLayouted(QFixed y) const
3054{
3055 Q_Q(const QTextDocumentLayout);
3056 if (currentLazyLayoutPosition == -1)
3057 return;
3058 const QSizeF oldSize = q->dynamicDocumentSize();
3059
3060 if (checkPoints.isEmpty())
3061 layoutStep();
3062
3063 while (currentLazyLayoutPosition != -1
3064 && checkPoints.last().y < y)
3065 layoutStep();
3066}
3067
3068void QTextDocumentLayoutPrivate::ensureLayoutedByPosition(int position) const
3069{
3070 if (currentLazyLayoutPosition == -1)
3071 return;
3072 if (position < currentLazyLayoutPosition)
3073 return;
3074 while (currentLazyLayoutPosition != -1
3075 && currentLazyLayoutPosition < position) {
3076 const_cast<QTextDocumentLayout *>(q_func())->doLayout(currentLazyLayoutPosition, 0, INT_MAX - currentLazyLayoutPosition);
3077 }
3078}
3079
3080void QTextDocumentLayoutPrivate::layoutStep() const
3081{
3082 ensureLayoutedByPosition(currentLazyLayoutPosition + lazyLayoutStepSize);
3083 lazyLayoutStepSize = qMin(200000, lazyLayoutStepSize * 2);
3084}
3085
3086void QTextDocumentLayout::setCursorWidth(int width)
3087{
3088 Q_D(QTextDocumentLayout);
3089 d->cursorWidth = width;
3090}
3091
3092int QTextDocumentLayout::cursorWidth() const
3093{
3094 Q_D(const QTextDocumentLayout);
3095 return d->cursorWidth;
3096}
3097
3098void QTextDocumentLayout::setFixedColumnWidth(int width)
3099{
3100 Q_D(QTextDocumentLayout);
3101 d->fixedColumnWidth = width;
3102}
3103
3104QRectF QTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const
3105{
3106 Q_D(const QTextDocumentLayout);
3107 if (d->docPrivate->pageSize.isNull())
3108 return QRectF();
3109 d->ensureLayoutFinished();
3110 return d->frameBoundingRectInternal(frame);
3111}
3112
3113QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame) const
3114{
3115 QPointF pos;
3116 const int framePos = frame->firstPosition();
3117 QTextFrame *f = frame;
3118 while (f) {
3119 QTextFrameData *fd = data(f);
3120 pos += fd->position.toPointF();
3121
3122 if (QTextTable *table = qobject_cast<QTextTable *>(f)) {
3123 QTextTableCell cell = table->cellAt(framePos);
3124 if (cell.isValid())
3125 pos += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
3126 }
3127
3128 f = f->parentFrame();
3129 }
3130 return QRectF(pos, data(frame)->size.toSizeF());
3131}
3132
3133QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
3134{
3135 Q_D(const QTextDocumentLayout);
3136 if (d->docPrivate->pageSize.isNull())
3137 return QRectF();
3138 d->ensureLayoutedByPosition(block.position() + block.length());
3139 QTextFrame *frame = d->document->frameAt(block.position());
3140 QPointF offset;
3141 const int blockPos = block.position();
3142
3143 while (frame) {
3144 QTextFrameData *fd = data(frame);
3145 offset += fd->position.toPointF();
3146
3147 if (QTextTable *table = qobject_cast<QTextTable *>(frame)) {
3148 QTextTableCell cell = table->cellAt(blockPos);
3149 if (cell.isValid())
3150 offset += static_cast<QTextTableData *>(fd)->cellPosition(cell).toPointF();
3151 }
3152
3153 frame = frame->parentFrame();
3154 }
3155
3156 const QTextLayout *layout = block.layout();
3157 QRectF rect = layout->boundingRect();
3158 rect.moveTopLeft(layout->position() + offset);
3159 return rect;
3160}
3161
3162int QTextDocumentLayout::layoutStatus() const
3163{
3164 Q_D(const QTextDocumentLayout);
3165 int pos = d->currentLazyLayoutPosition;
3166 if (pos == -1)
3167 return 100;
3168 return pos * 100 / d->document->docHandle()->length();
3169}
3170
3171void QTextDocumentLayout::timerEvent(QTimerEvent *e)
3172{
3173 Q_D(QTextDocumentLayout);
3174 if (e->timerId() == d->layoutTimer.timerId()) {
3175 if (d->currentLazyLayoutPosition != -1)
3176 d->layoutStep();
3177 } else if (e->timerId() == d->sizeChangedTimer.timerId()) {
3178 d->lastReportedSize = dynamicDocumentSize();
3179 emit documentSizeChanged(d->lastReportedSize);
3180 d->sizeChangedTimer.stop();
3181
3182 if (d->currentLazyLayoutPosition == -1) {
3183 const int newCount = dynamicPageCount();
3184 if (newCount != d->lastPageCount) {
3185 d->lastPageCount = newCount;
3186 emit pageCountChanged(newCount);
3187 }
3188 }
3189 } else {
3190 QAbstractTextDocumentLayout::timerEvent(e);
3191 }
3192}
3193
3194void QTextDocumentLayout::layoutFinished()
3195{
3196 Q_D(QTextDocumentLayout);
3197 d->layoutTimer.stop();
3198 if (!d->insideDocumentChange)
3199 d->sizeChangedTimer.start(0, this);
3200 // reset
3201 d->showLayoutProgress = true;
3202}
3203
3204void QTextDocumentLayout::ensureLayouted(qreal y)
3205{
3206 d_func()->ensureLayouted(QFixed::fromReal(y));
3207}
3208
3209qreal QTextDocumentLayout::idealWidth() const
3210{
3211 Q_D(const QTextDocumentLayout);
3212 d->ensureLayoutFinished();
3213 return d->idealWidth;
3214}
3215
3216bool QTextDocumentLayout::contentHasAlignment() const
3217{
3218 Q_D(const QTextDocumentLayout);
3219 return d->contentHasAlignment;
3220}
3221
3222qreal QTextDocumentLayoutPrivate::scaleToDevice(qreal value) const
3223{
3224 if (!paintDevice)
3225 return value;
3226 return value * paintDevice->logicalDpiY() / qreal(qt_defaultDpi());
3227}
3228
3229QFixed QTextDocumentLayoutPrivate::scaleToDevice(QFixed value) const
3230{
3231 if (!paintDevice)
3232 return value;
3233 return value * QFixed(paintDevice->logicalDpiY()) / QFixed(qt_defaultDpi());
3234}
3235
3236QT_END_NAMESPACE
3237
3238#include "moc_qtextdocumentlayout_p.cpp"
Note: See TracBrowser for help on using the repository browser.