Warning, file /office/calligra/libs/textlayout/KoTextLayoutArea.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* This file is part of the KDE project 0002 * Copyright (C) 2006-2010 Thomas Zander <zander@kde.org> 0003 * Copyright (C) 2008,2011 Thorsten Zachmann <zachmann@kde.org> 0004 * Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in> 0005 * Copyright (C) 2008 Roopesh Chander <roop@forwardbias.in> 0006 * Copyright (C) 2007-2008 Pierre Ducroquet <pinaraf@pinaraf.info> 0007 * Copyright (C) 2009-2011 KO GmbH <cbo@kogmbh.com> 0008 * Copyright (C) 2009-2011 C. Boemann <cbo@boemann.dk> 0009 * Copyright (C) 2010 Nandita Suri <suri.nandita@gmail.com> 0010 * Copyright (C) 2010 Ajay Pundhir <ajay.pratap@iiitb.net> 0011 * Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.com> 0012 * Copyright (C) 2011 Gopalakrishna Bhat A <gopalakbhat@gmail.com> 0013 * Copyright (C) 2011 Stuart Dickson <stuart@furkinfantasic.net> 0014 * 0015 * This library is free software; you can redistribute it and/or 0016 * modify it under the terms of the GNU Library General Public 0017 * License as published by the Free Software Foundation; either 0018 * version 2 of the License, or (at your option) any later version. 0019 * 0020 * This library is distributed in the hope that it will be useful, 0021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0023 * Library General Public License for more details. 0024 * 0025 * You should have received a copy of the GNU Library General Public License 0026 * along with this library; see the file COPYING.LIB. If not, write to 0027 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0028 * Boston, MA 02110-1301, USA. 0029 */ 0030 0031 #include "KoTextLayoutArea.h" 0032 #include "KoTextLayoutArea_p.h" 0033 0034 #include "TableIterator.h" 0035 #include "ListItemsHelper.h" 0036 #include "RunAroundHelper.h" 0037 #include "KoTextDocumentLayout.h" 0038 #include "FrameIterator.h" 0039 #include "KoPointedAt.h" 0040 #include "KoCharAreaInfo.h" 0041 0042 #include <KoTextDocument.h> 0043 #include <KoParagraphStyle.h> 0044 #include <KoCharacterStyle.h> 0045 #include <KoListStyle.h> 0046 #include <KoTableStyle.h> 0047 #include <KoStyleManager.h> 0048 #include <KoTextBlockData.h> 0049 #include <KoText.h> 0050 #include <KoChangeTracker.h> 0051 #include <KoChangeTrackerElement.h> 0052 #include <KoInlineNote.h> 0053 #include <KoTextSoftPageBreak.h> 0054 #include <KoInlineTextObjectManager.h> 0055 0056 #include <TextLayoutDebug.h> 0057 0058 #include <QTextFrame> 0059 #include <QTextTable> 0060 #include <QTextList> 0061 #include <QStyle> 0062 #include <QFontMetrics> 0063 #include <QTextFragment> 0064 #include <QTextLayout> 0065 #include <QTextCursor> 0066 0067 #include <algorithm> 0068 0069 extern int qt_defaultDpiY(); 0070 Q_DECLARE_METATYPE(QTextDocument *) 0071 0072 #define DropCapsAdditionalFormattingId 25602902 0073 #define PresenterFontStretch 1.2 0074 0075 KoTextLayoutArea::KoTextLayoutArea(KoTextLayoutArea *p, KoTextDocumentLayout *documentLayout) 0076 : d (new Private) 0077 { 0078 d->parent = p; 0079 d->documentLayout = documentLayout; 0080 } 0081 0082 KoTextLayoutArea::~KoTextLayoutArea() 0083 { 0084 qDeleteAll(d->tableAreas); 0085 qDeleteAll(d->footNoteAreas); 0086 qDeleteAll(d->preregisteredFootNoteAreas); 0087 delete d->startOfArea; 0088 delete d->endOfArea; 0089 delete d; 0090 } 0091 0092 0093 KoPointedAt KoTextLayoutArea::hitTest(const QPointF &p, Qt::HitTestAccuracy accuracy) const 0094 { 0095 QPointF point = p - QPointF(0, d->verticalAlignOffset); 0096 0097 if (d->startOfArea == 0) // We have not been layouted yet 0098 return KoPointedAt(); 0099 0100 KoPointedAt pointedAt; 0101 bool basicallyFound = false; 0102 0103 QTextFrame::iterator it = d->startOfArea->it; 0104 QTextFrame::iterator stop = d->endOfArea->it; 0105 if (!stop.atEnd()) { 0106 if(!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { 0107 // Last thing we contain is a frame (table) or first part of a paragraph split in two 0108 // The stop point should be the object after that 0109 // However if stop is already atEnd we shouldn't increment further 0110 ++stop; 0111 } 0112 } 0113 int tableAreaIndex = 0; 0114 int tocIndex = 0; 0115 int footNoteIndex = 0; 0116 for (; it != stop && !it.atEnd(); ++it) { 0117 QTextBlock block = it.currentBlock(); 0118 QTextTable *table = qobject_cast<QTextTable*>(it.currentFrame()); 0119 QTextFrame *subFrame = it.currentFrame(); 0120 0121 if (table) { 0122 if (tableAreaIndex >= d->tableAreas.size()) { 0123 continue; 0124 } 0125 if (point.y() > d->tableAreas[tableAreaIndex]->top() 0126 && point.y() < d->tableAreas[tableAreaIndex]->bottom()) { 0127 return d->tableAreas[tableAreaIndex]->hitTest(point, accuracy); 0128 } 0129 ++tableAreaIndex; 0130 continue; 0131 } else if (subFrame) { 0132 if (it.currentFrame()->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { 0133 if (point.y() > d->endNotesArea->top() 0134 && point.y() < d->endNotesArea->bottom()) { 0135 pointedAt = d->endNotesArea->hitTest(point, accuracy); 0136 return pointedAt; 0137 } 0138 } 0139 break; 0140 } else { 0141 if (!block.isValid()) 0142 continue; 0143 } 0144 if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { 0145 // check if p is over table of content 0146 if (point.y() > d->generatedDocAreas[tocIndex]->top() 0147 && point.y() < d->generatedDocAreas[tocIndex]->bottom()) { 0148 pointedAt = d->generatedDocAreas[tocIndex]->hitTest(point, accuracy); 0149 pointedAt.position = block.position(); 0150 return pointedAt; 0151 } 0152 ++tocIndex; 0153 continue; 0154 } 0155 if (basicallyFound) // a subsequent table or lines have now had their chance 0156 return pointedAt; 0157 0158 QTextLayout *layout = block.layout(); 0159 QTextFrame::iterator next = it; 0160 ++next; 0161 if (next != stop && next.currentFrame() == 0 && point.y() > layout->boundingRect().bottom()) { 0162 // just skip this block. 0163 continue; 0164 } 0165 0166 for (int i = 0; i < layout->lineCount(); i++) { 0167 QTextLine line = layout->lineAt(i); 0168 if (block == d->startOfArea->it.currentBlock() && line.textStart() < d->startOfArea->lineTextStart) { 0169 continue; // this line is part of a previous layoutArea 0170 } 0171 if (point.y() > line.y() + line.height()) { 0172 pointedAt.position = block.position() + line.textStart() + line.textLength(); 0173 if (block == d->endOfArea->it.currentBlock() && line.textStart() + line.textLength() >= d->endOfArea->lineTextStart) { 0174 pointedAt.position = block.position() + line.xToCursor(point.x()); 0175 break; // this and following lines are part of a next layoutArea 0176 } 0177 continue; 0178 } 0179 if (accuracy == Qt::ExactHit && point.y() < line.y()) { // between lines 0180 return KoPointedAt(); 0181 } 0182 const QRectF lineRect = line.naturalTextRect(); 0183 if (accuracy == Qt::ExactHit && // left or right of line 0184 (point.x() < lineRect.left() || point.x() > lineRect.right())) { 0185 return KoPointedAt(); 0186 } 0187 if (point.x() > lineRect.x() + lineRect.width() && layout->textOption().textDirection() == Qt::RightToLeft) { 0188 // totally right of RTL text means the position is the start of the text. 0189 //TODO how about the other side? 0190 pointedAt.position = block.position() + line.textStart(); 0191 return pointedAt; 0192 } 0193 if (basicallyFound && point.y() < lineRect.y()) { 0194 // This was not same baseline so basicallyFound was correct 0195 return pointedAt; 0196 } 0197 if (point.x() > lineRect.x() + lineRect.width()) { 0198 // right of line 0199 basicallyFound = true; 0200 pointedAt.position = block.position() + line.textStart() + line.textLength(); 0201 continue; // don't break as next line may be on same baseline 0202 } 0203 pointedAt.position = block.position() + line.xToCursor(point.x()); 0204 QTextCursor tmpCursor(block); 0205 tmpCursor.setPosition(block.position() + line.xToCursor(point.x(), QTextLine::CursorOnCharacter) + 1); 0206 pointedAt.fillInLinks(tmpCursor, d->documentLayout->inlineTextObjectManager(), d->documentLayout->textRangeManager()); 0207 return pointedAt; 0208 } 0209 } 0210 0211 //and finally test the footnotes 0212 point -= QPointF(0, bottom() - d->footNotesHeight); 0213 while (footNoteIndex < d->footNoteAreas.length()) { 0214 // check if p is over foot notes area 0215 if (point.y() > 0 && point.y() < d->footNoteAreas[footNoteIndex]->bottom() 0216 - d->footNoteAreas[footNoteIndex]->top()) { 0217 pointedAt = d->footNoteAreas[footNoteIndex]->hitTest(point, accuracy); 0218 return pointedAt; 0219 } 0220 point -= QPointF(0, d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top()); 0221 ++footNoteIndex; 0222 } 0223 return pointedAt; 0224 } 0225 0226 0227 QVector<KoCharAreaInfo> KoTextLayoutArea::generateCharAreaInfos() const 0228 { 0229 QVector<KoCharAreaInfo> result; 0230 if (d->startOfArea == 0 || d->endOfArea == 0) { // We have not been completely layouted yet 0231 debugTextLayout << "called when not completely layouted yet"; 0232 return result; 0233 } 0234 0235 QTextFrame::iterator it = d->startOfArea->it; 0236 QTextFrame::iterator stop = d->endOfArea->it; 0237 0238 int tableAreaIndex = 0; 0239 int tocIndex = 0; 0240 for (; it != stop && !it.atEnd(); ++it) { 0241 QTextTable *table = qobject_cast<QTextTable*>(it.currentFrame()); 0242 if (table) { 0243 if (tableAreaIndex >= d->tableAreas.size()) { 0244 continue; 0245 } 0246 result.append(d->tableAreas[tableAreaIndex]->generateCharAreaInfos()); 0247 ++tableAreaIndex; 0248 continue; 0249 } 0250 0251 QTextFrame *subFrame = it.currentFrame(); 0252 if (subFrame) { 0253 if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { 0254 result.append(d->endNotesArea->generateCharAreaInfos()); 0255 } 0256 continue; 0257 } 0258 0259 QTextBlock block = it.currentBlock(); 0260 if (!block.isValid()) { 0261 continue; 0262 } 0263 0264 if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { 0265 result.append(d->generatedDocAreas[tocIndex]->generateCharAreaInfos()); 0266 ++tocIndex; 0267 continue; 0268 } 0269 0270 // TODO: also include header/paragraph numbering/bullet points 0271 QTextLayout *layout = block.layout(); 0272 0273 for (int i = 0; i < layout->lineCount(); ++i) { 0274 QTextLine line = layout->lineAt(i); 0275 if (block == d->startOfArea->it.currentBlock() && line.textStart() < d->startOfArea->lineTextStart) { 0276 continue; // this line is part of a previous layoutArea 0277 } 0278 if (block == d->endOfArea->it.currentBlock() && line.textStart() + line.textLength() >= d->endOfArea->lineTextStart) { 0279 break; // this and following lines are part of a next layoutArea 0280 } 0281 qreal xLeading; 0282 qreal xTrailing; 0283 for (int j = line.textStart(); j < line.textStart() + line.textLength(); ++j) { 0284 // TODO: support RTL 0285 xLeading = line.cursorToX(j, QTextLine::Leading); 0286 xTrailing = line.cursorToX(j, QTextLine::Trailing); 0287 QRectF rect(xLeading, line.y(), xTrailing-xLeading, line.height()); // TODO: at least height needs more work 0288 result.append(KoCharAreaInfo(rect, block.text().at(j))); 0289 } 0290 0291 // TODO: perhaps only at end of paragraph (last qtextline) add linebreak, for in-paragraph linebreak 0292 // use real whitespace(s) found in original text (or see if forced linebreak) 0293 QRectF rect(xTrailing, line.y(), 1, line.height()); // TODO: better dummy width needed, with reasoning 0294 result.append(KoCharAreaInfo(rect, QLatin1Char('\n'))); 0295 } 0296 } 0297 0298 qreal footNoteYOffset = bottom() - d->footNotesHeight; 0299 foreach(KoTextLayoutNoteArea *footerArea, d->footNoteAreas) { 0300 QVector<KoCharAreaInfo> footNoteCharAreaInfos = footerArea->generateCharAreaInfos(); 0301 QMutableVectorIterator<KoCharAreaInfo> it(footNoteCharAreaInfos); 0302 while (it.hasNext()) { 0303 KoCharAreaInfo &info = it.next(); 0304 info.rect.translate(0, footNoteYOffset); 0305 } 0306 result.append(footNoteCharAreaInfos); 0307 footNoteYOffset += footerArea->bottom() - footerArea->top(); 0308 } 0309 0310 return result; 0311 } 0312 0313 0314 QRectF KoTextLayoutArea::selectionBoundingBox(QTextCursor &cursor) const 0315 { 0316 QRectF retval(-5E6, top(), 105E6, 0); 0317 0318 if (d->startOfArea == 0) // We have not been layouted yet 0319 return QRectF(); 0320 if (d->endOfArea == 0) // no end area yet 0321 return QRectF(); 0322 0323 QTextFrame::iterator it = d->startOfArea->it; 0324 QTextFrame::iterator stop = d->endOfArea->it; 0325 if (!stop.atEnd()) { 0326 if(!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { 0327 // Last thing we show is a frame (table) or first part of a paragraph split in two 0328 // The stop point should be the object after that 0329 // However if stop is already atEnd we shouldn't increment further 0330 ++stop; 0331 } 0332 } 0333 0334 QTextFrame *subFrame; 0335 int footNoteIndex = 0; 0336 qreal offset = bottom() - d->footNotesHeight; 0337 while (footNoteIndex < d->footNoteAreas.length()) { 0338 subFrame = d->footNoteFrames[footNoteIndex]; 0339 if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { 0340 return d->footNoteAreas[footNoteIndex]->selectionBoundingBox(cursor).translated(0, offset) ; 0341 } 0342 offset += d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top(); 0343 ++footNoteIndex; 0344 } 0345 0346 int tableAreaIndex = 0; 0347 int tocIndex = 0; 0348 0349 for (; it != stop && !it.atEnd(); ++it) { 0350 QTextBlock block = it.currentBlock(); 0351 QTextTable *table = qobject_cast<QTextTable*>(it.currentFrame()); 0352 QTextFrame *subFrame = it.currentFrame(); 0353 0354 if (table) { 0355 if (tableAreaIndex >= d->tableAreas.size()) { 0356 continue; 0357 } 0358 if (cursor.selectionEnd() < table->firstPosition()) { 0359 return retval.translated(0, d->verticalAlignOffset); 0360 } 0361 if (cursor.selectionStart() > table->lastPosition()) { 0362 ++tableAreaIndex; 0363 continue; 0364 } 0365 if (cursor.selectionStart() >= table->firstPosition() && cursor.selectionEnd() <= table->lastPosition()) { 0366 return d->tableAreas[tableAreaIndex]->selectionBoundingBox(cursor).translated(0, d->verticalAlignOffset); 0367 } 0368 if (cursor.selectionStart() >= table->firstPosition()) { 0369 retval = d->tableAreas[tableAreaIndex]->boundingRect(); 0370 } else { 0371 retval |= d->tableAreas[tableAreaIndex]->boundingRect(); 0372 } 0373 ++tableAreaIndex; 0374 continue; 0375 } else if (subFrame) { 0376 if (it.currentFrame()->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { 0377 if (cursor.selectionEnd() < subFrame->firstPosition()) { 0378 return retval.translated(0, d->verticalAlignOffset); 0379 } 0380 if (cursor.selectionStart() > subFrame->lastPosition()) { 0381 break; 0382 } 0383 if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { 0384 return d->endNotesArea->selectionBoundingBox(cursor).translated(0, d->verticalAlignOffset); 0385 } 0386 break; 0387 } 0388 } else { 0389 if (!block.isValid()) 0390 continue; 0391 } 0392 if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { 0393 if (cursor.selectionStart() <= block.position() 0394 && cursor.selectionEnd() >= block.position()) { 0395 retval |= d->generatedDocAreas[tocIndex]->boundingRect(); 0396 } 0397 ++tocIndex; 0398 continue; 0399 } 0400 0401 if(cursor.selectionEnd() < block.position()) { 0402 return retval.translated(0, d->verticalAlignOffset); 0403 } 0404 if(cursor.selectionStart() >= block.position() 0405 && cursor.selectionStart() < block.position() + block.length()) { 0406 QTextLine line = block.layout()->lineForTextPosition(cursor.selectionStart() - block.position()); 0407 if (line.isValid()) { 0408 retval.setTop(line.y()); 0409 retval.setBottom(line.y()); 0410 } 0411 } 0412 if(cursor.selectionEnd() >= block.position() 0413 && cursor.selectionEnd() < block.position() + block.length()) { 0414 QTextLine line = block.layout()->lineForTextPosition(cursor.selectionEnd() - block.position()); 0415 if (line.isValid()) { 0416 retval.setBottom(line.y() + line.height()); 0417 if (line.ascent()==0) { 0418 // Block is empty from any visible content and has as such no height 0419 // but in that case the block font defines line height 0420 retval.setBottom(line.y() + 24); 0421 } 0422 0423 if (cursor.selectionStart() == cursor.selectionEnd()) { 0424 // We only have a caret so let's set the rect a bit more narrow 0425 retval.setX(line.cursorToX(cursor.position() - block.position())); 0426 retval.setWidth(1); 0427 } 0428 } 0429 } 0430 // if the full paragraph is selected to add it to the rect. This makes sure we get a rect for the case 0431 // where the end of the selection lies is a different area. 0432 if (cursor.selectionEnd() >= block.position() + block.length() && cursor.selectionStart() <= block.position()) { 0433 QTextLine line = block.layout()->lineForTextPosition(block.length()-1); 0434 if (line.isValid()) { 0435 retval.setBottom(line.y() + line.height()); 0436 if (line.ascent()==0) { 0437 // Block is empty from any visible content and has as such no height 0438 // but in that case the block font defines line height 0439 retval.setBottom(line.y() + 24); 0440 } 0441 } 0442 } 0443 } 0444 return retval.translated(0, d->verticalAlignOffset); 0445 } 0446 0447 0448 bool KoTextLayoutArea::isStartingAt(FrameIterator *cursor) const 0449 { 0450 if (d->startOfArea) { 0451 return *d->startOfArea == *cursor; 0452 } 0453 0454 return false; 0455 } 0456 0457 QTextFrame::iterator KoTextLayoutArea::startTextFrameIterator() const 0458 { 0459 return d->startOfArea->it; 0460 } 0461 0462 QTextFrame::iterator KoTextLayoutArea::endTextFrameIterator() const 0463 { 0464 return d->endOfArea->it; 0465 } 0466 0467 void KoTextLayoutArea::backtrackKeepWithNext(FrameIterator *cursor) 0468 { 0469 QTextFrame::iterator it = cursor->it; 0470 0471 while (!(it == d->startOfArea->it)) { 0472 --it; 0473 QTextBlock block = it.currentBlock(); 0474 QTextTable *table = qobject_cast<QTextTable*>(it.currentFrame()); 0475 QTextFrame *subFrame = it.currentFrame(); 0476 bool keepWithNext = false; 0477 if (table) { 0478 keepWithNext = table->format().boolProperty(KoTableStyle::KeepWithNext); 0479 //setBottom(tableArea->bottom() + d->footNotesHeight); 0480 } else if (subFrame) { 0481 Q_ASSERT(false); // there should never be an aux frame before normal layouted stuff 0482 } else if (block.isValid()) { 0483 keepWithNext = block.blockFormat().boolProperty(KoParagraphStyle::KeepWithNext); 0484 //setBottom(d->blockRects.last()->bottom() + d->footNotesHeight); 0485 } 0486 if (!keepWithNext) { 0487 cursor->it = ++it; 0488 break; 0489 } 0490 } 0491 } 0492 0493 bool KoTextLayoutArea::layout(FrameIterator *cursor) 0494 { 0495 qDeleteAll(d->tableAreas); 0496 d->tableAreas.clear(); 0497 qDeleteAll(d->footNoteAreas); 0498 d->footNoteAreas.clear(); 0499 qDeleteAll(d->preregisteredFootNoteAreas); 0500 d->preregisteredFootNoteAreas.clear(); 0501 d->footNoteFrames.clear(); 0502 d->preregisteredFootNoteFrames.clear(); 0503 qDeleteAll(d->generatedDocAreas); 0504 d->generatedDocAreas.clear(); 0505 d->blockRects.clear(); 0506 delete d->endNotesArea; 0507 d->endNotesArea=0; 0508 if (d->copyEndOfArea && !d->copyEndOfArea->isValid()) { 0509 delete d->copyEndOfArea; 0510 d->copyEndOfArea = 0; 0511 } 0512 if (d->endOfArea && d->endOfArea->isValid()) { 0513 delete d->copyEndOfArea; 0514 d->copyEndOfArea = new FrameIterator(d->endOfArea); 0515 } 0516 delete d->startOfArea; 0517 delete d->endOfArea; 0518 d->dropCapsWidth = 0; 0519 d->dropCapsDistance = 0; 0520 0521 d->startOfArea = new FrameIterator(cursor); 0522 d->endOfArea = 0; 0523 d->y = top(); 0524 d->neededWidth = 0; 0525 setBottom(top()); 0526 d->bottomSpacing = 0; 0527 d->footNoteAutoCount = 0; 0528 d->footNotesHeight = 0; 0529 d->preregisteredFootNotesHeight = 0; 0530 d->prevBorder = 0; 0531 d->prevBorderPadding = 0; 0532 0533 if (d->footNoteCursorFromPrevious) { 0534 KoTextLayoutNoteArea *footNoteArea = new KoTextLayoutNoteArea(d->continuedNoteFromPrevious, this, d->documentLayout); 0535 d->footNoteFrames.append(d->continuedNoteFromPrevious->textFrame()); 0536 footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom()); 0537 footNoteArea->setAsContinuedArea(true); 0538 footNoteArea->layout(d->footNoteCursorFromPrevious); 0539 d->footNotesHeight += footNoteArea->bottom() - footNoteArea->top(); 0540 d->footNoteAreas.append(footNoteArea); 0541 } 0542 while (!cursor->it.atEnd()) { 0543 QTextBlock block = cursor->it.currentBlock(); 0544 QTextTable *table = qobject_cast<QTextTable*>(cursor->it.currentFrame()); 0545 QTextFrame *subFrame = cursor->it.currentFrame(); 0546 if (table) { 0547 QString masterPageName = table->frameFormat().property(KoTableStyle::MasterPageName).toString(); 0548 bool masterPageNameChanged = !masterPageName.isEmpty(); 0549 if (masterPageNameChanged) { 0550 cursor->masterPageName = masterPageName; 0551 } 0552 0553 if (!virginPage()) { 0554 int breaktype = table->frameFormat().intProperty(KoTableStyle::BreakBefore); 0555 if ((acceptsPageBreak() && (masterPageNameChanged || (breaktype == KoText::PageBreak))) 0556 || (acceptsColumnBreak() && (breaktype == KoText::ColumnBreak))) { 0557 d->endOfArea = new FrameIterator(cursor); 0558 setBottom(d->y + d->footNotesHeight); 0559 if (!d->blockRects.isEmpty()) { 0560 d->blockRects.last().setBottom(d->y); 0561 } 0562 return false; 0563 } 0564 } 0565 0566 // Let's create KoTextLayoutTableArea and let that handle the table 0567 KoTextLayoutTableArea *tableArea = new KoTextLayoutTableArea(table, this, d->documentLayout); 0568 d->tableAreas.append(tableArea); 0569 d->y += d->bottomSpacing; 0570 if (!d->blockRects.isEmpty()) { 0571 d->blockRects.last().setBottom(d->y); 0572 } 0573 tableArea->setVirginPage(virginPage()); 0574 tableArea->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); 0575 if (tableArea->layoutTable(cursor->tableIterator(table)) == false) { 0576 d->endOfArea = new FrameIterator(cursor); 0577 d->y = tableArea->bottom(); 0578 setBottom(d->y + d->footNotesHeight); 0579 // Expand bounding rect so if we have content outside we show it 0580 expandBoundingLeft(tableArea->boundingRect().left()); 0581 expandBoundingRight(tableArea->boundingRect().right()); 0582 0583 return false; 0584 } 0585 setVirginPage(false); 0586 // Expand bounding rect so if we have content outside we show it 0587 expandBoundingLeft(tableArea->boundingRect().left()); 0588 expandBoundingRight(tableArea->boundingRect().right()); 0589 d->bottomSpacing = 0; 0590 d->y = tableArea->bottom(); 0591 delete cursor->currentTableIterator; 0592 cursor->currentTableIterator = 0; 0593 } else if (subFrame) { 0594 if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { 0595 Q_ASSERT(d->endNotesArea == 0); 0596 d->endNotesArea = new KoTextLayoutEndNotesArea(this, d->documentLayout); 0597 d->y += d->bottomSpacing; 0598 if (!d->blockRects.isEmpty()) { 0599 d->blockRects.last().setBottom(d->y); 0600 } 0601 d->endNotesArea->setVirginPage(virginPage()); 0602 d->endNotesArea->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); 0603 if (d->endNotesArea->layout(cursor->subFrameIterator(subFrame)) == false) { 0604 d->endOfArea = new FrameIterator(cursor); 0605 d->y = d->endNotesArea->bottom(); 0606 setBottom(d->y + d->footNotesHeight); 0607 // Expand bounding rect so if we have content outside we show it 0608 expandBoundingLeft(d->endNotesArea->boundingRect().left()); 0609 expandBoundingRight(d->endNotesArea->boundingRect().right()); 0610 return false; 0611 } 0612 setVirginPage(false); 0613 // Expand bounding rect so if we have content outside we show it 0614 expandBoundingLeft(d->endNotesArea->boundingRect().left()); 0615 expandBoundingRight(d->endNotesArea->boundingRect().right()); 0616 d->bottomSpacing = 0; 0617 d->y = d->endNotesArea->bottom(); 0618 delete cursor->currentSubFrameIterator; 0619 cursor->currentSubFrameIterator = 0; 0620 0621 // we have layouted till the end of the document except for a blank block 0622 // which we should ignore 0623 ++(cursor->it); 0624 ++(cursor->it); 0625 break; 0626 } 0627 } else if (block.isValid()) { 0628 if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { 0629 QVariant data = block.blockFormat().property(KoParagraphStyle::GeneratedDocument); 0630 QTextDocument *generatedDocument = data.value<QTextDocument *>(); 0631 0632 // Let's create KoTextLayoutArea and let it handle the generated document 0633 KoTextLayoutArea *area = new KoTextLayoutArea(this, documentLayout()); 0634 d->generatedDocAreas.append(area); 0635 d->y += d->bottomSpacing; 0636 if (!d->blockRects.isEmpty()) { 0637 d->blockRects.last().setBottom(d->y); 0638 } 0639 area->setVirginPage(virginPage()); 0640 area->setAcceptsPageBreak(acceptsPageBreak()); 0641 area->setAcceptsColumnBreak(acceptsColumnBreak()); 0642 area->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); 0643 QTextLayout *blayout = block.layout(); 0644 blayout->beginLayout(); 0645 QTextLine line = blayout->createLine(); 0646 line.setNumColumns(0); 0647 line.setPosition(QPointF(left(), d->y)); 0648 blayout->endLayout(); 0649 0650 if (area->layout(cursor->subFrameIterator(generatedDocument->rootFrame())) == false) { 0651 cursor->lineTextStart = 1; // fake we are not done 0652 d->endOfArea = new FrameIterator(cursor); 0653 d->y = area->bottom(); 0654 setBottom(d->y + d->footNotesHeight); 0655 // Expand bounding rect so if we have content outside we show it 0656 expandBoundingLeft(area->boundingRect().left()); 0657 expandBoundingRight(area->boundingRect().right()); 0658 return false; 0659 } 0660 setVirginPage(false); 0661 // Expand bounding rect so if we have content outside we show it 0662 expandBoundingLeft(area->boundingRect().left()); 0663 expandBoundingRight(area->boundingRect().right()); 0664 d->bottomSpacing = 0; 0665 d->y = area->bottom(); 0666 delete cursor->currentSubFrameIterator; 0667 cursor->lineTextStart = -1; // fake we are done 0668 cursor->currentSubFrameIterator = 0; 0669 } else { 0670 // FIXME this doesn't work for cells inside tables. We probably should make it more 0671 // generic to handle such cases too. 0672 QString masterPageName = block.blockFormat().property(KoParagraphStyle::MasterPageName).toString(); 0673 bool masterPageNameChanged = !masterPageName.isEmpty(); 0674 if (masterPageNameChanged) { 0675 cursor->masterPageName = masterPageName; 0676 } 0677 0678 if (!virginPage()) { 0679 int breaktype = block.blockFormat().intProperty(KoParagraphStyle::BreakBefore); 0680 if ((acceptsPageBreak() && (masterPageNameChanged || (breaktype == KoText::PageBreak))) 0681 ||(acceptsColumnBreak() && (breaktype == KoText::ColumnBreak))) { 0682 d->endOfArea = new FrameIterator(cursor); 0683 setBottom(d->y + d->footNotesHeight); 0684 if (!d->blockRects.isEmpty()) { 0685 d->blockRects.last().setBottom(d->y); 0686 } 0687 return false; 0688 } 0689 } 0690 0691 if (layoutBlock(cursor) == false) { 0692 if (cursor->lineTextStart == -1) { 0693 //Nothing was added so lets backtrack keep-with-next 0694 backtrackKeepWithNext(cursor); 0695 } 0696 d->endOfArea = new FrameIterator(cursor); 0697 setBottom(d->y + d->footNotesHeight); 0698 d->blockRects.last().setBottom(d->y); 0699 return false; 0700 } 0701 d->extraTextIndent = 0; 0702 0703 int breaktype = block.blockFormat().intProperty(KoParagraphStyle::BreakAfter); 0704 if ((acceptsPageBreak() && (breaktype & KoText::PageBreak)) 0705 || (acceptsColumnBreak() && (breaktype & KoText::ColumnBreak))) { 0706 Q_ASSERT(!cursor->it.atEnd()); 0707 QTextFrame::iterator nextIt = cursor->it; 0708 ++nextIt; 0709 bool wasIncremented = !nextIt.currentFrame(); 0710 if (wasIncremented) 0711 cursor->it = nextIt; 0712 d->endOfArea = new FrameIterator(cursor); 0713 if (!wasIncremented) 0714 ++(cursor->it); 0715 setBottom(d->y + d->footNotesHeight); 0716 d->blockRects.last().setBottom(d->y); 0717 return false; 0718 } 0719 } 0720 } 0721 bool atEnd = cursor->it.atEnd(); 0722 if (!atEnd) { 0723 ++(cursor->it); 0724 } 0725 } 0726 d->endOfArea = new FrameIterator(cursor); 0727 d->y = qMin(maximumAllowedBottom(), d->y + d->bottomSpacing); 0728 setBottom(d->y + d->footNotesHeight); 0729 if (!d->blockRects.isEmpty()) { 0730 d->blockRects.last().setBottom(d->y); 0731 } 0732 if (d->maximumAllowedWidth>0) { 0733 d->right += d->neededWidth - d->width; 0734 d->maximumAllowedWidth = 0; 0735 setVirginPage(true); 0736 KoTextLayoutArea::layout(new FrameIterator(d->startOfArea)); 0737 } 0738 return true; // we have layouted till the end of the frame 0739 } 0740 0741 0742 QTextLine KoTextLayoutArea::Private::restartLayout(QTextBlock &block, int lineTextStartOfLastKeep) 0743 { 0744 QTextLayout *layout = block.layout(); 0745 KoTextBlockData blockData(block); 0746 QPointF stashedCounterPosition = blockData.counterPosition(); 0747 QVector<LineKeeper> stashedLines; 0748 QTextLine line; 0749 for(int i = 0; i < layout->lineCount(); i++) { 0750 QTextLine l = layout->lineAt(i); 0751 if (l.textStart() >= lineTextStartOfLastKeep) { 0752 break; 0753 } 0754 LineKeeper lk; 0755 lk.lineWidth = l.width(); 0756 lk.columns = l.textLength(); 0757 lk.position = l.position(); 0758 stashedLines.append(lk); 0759 } 0760 layout->clearLayout(); 0761 layout->beginLayout(); 0762 line = layout->createLine(); 0763 0764 return recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); 0765 } 0766 0767 void KoTextLayoutArea::Private::stashRemainingLayout(QTextBlock &block, int lineTextStartOfFirstKeep, QVector<LineKeeper> &stashedLines, QPointF &stashedCounterPosition) 0768 { 0769 QTextLayout *layout = block.layout(); 0770 KoTextBlockData blockData(block); 0771 stashedCounterPosition = blockData.counterPosition(); 0772 QTextLine line; 0773 for(int i = 0; i < layout->lineCount(); i++) { 0774 QTextLine l = layout->lineAt(i); 0775 if (l.textStart() < lineTextStartOfFirstKeep) { 0776 continue; 0777 } 0778 LineKeeper lk; 0779 lk.lineWidth = l.width(); 0780 lk.columns = l.textLength(); 0781 lk.position = l.position(); 0782 stashedLines.append(lk); 0783 } 0784 } 0785 0786 QTextLine KoTextLayoutArea::Private::recreatePartialLayout(QTextBlock &block, const QVector<LineKeeper> &stashedLines, QPointF &stashedCounterPosition, QTextLine &line) 0787 { 0788 QTextLayout *layout = block.layout(); 0789 KoTextBlockData blockData(block); 0790 documentLayout->allowPositionInlineObject(false); 0791 if (layout->lineCount() == 1) { 0792 blockData.setCounterPosition(stashedCounterPosition); 0793 } 0794 foreach(const LineKeeper &lk, stashedLines) { 0795 line.setLineWidth(lk.lineWidth); 0796 if (lk.columns != line.textLength()) { 0797 // As setNumColumns might break differently we only use it if setLineWidth doesn't give 0798 // the same textLength as we had before 0799 line.setNumColumns(lk.columns, lk.lineWidth); 0800 } 0801 line.setPosition(lk.position); 0802 0803 line = layout->createLine(); 0804 if (!line.isValid()) 0805 break; 0806 } 0807 documentLayout->allowPositionInlineObject(true); 0808 return line; 0809 } 0810 0811 static bool compareTab(const QTextOption::Tab &tab1, const QTextOption::Tab &tab2) 0812 { 0813 return tab1.position < tab2.position; 0814 } 0815 0816 // layoutBlock() method is structured like this: 0817 // 0818 // 1) Setup various helper values 0819 // a) related to or influenced by lists 0820 // b) related to or influenced by dropcaps 0821 // c) related to or influenced by margins 0822 // d) related to or influenced by tabs 0823 // e) related to or influenced by borders 0824 // f) related to or influenced by list counters 0825 // 2)layout each line (possibly restarting where we stopped earlier) 0826 // a) fit line into sub lines with as needed for text runaround 0827 // b) break if we encounter softbreak 0828 // c) make sure we keep above maximumAllowedBottom 0829 // d) calls addLine() 0830 // e) update dropcaps related variables 0831 bool KoTextLayoutArea::layoutBlock(FrameIterator *cursor) 0832 { 0833 QTextBlock block(cursor->it.currentBlock()); 0834 KoTextBlockData blockData(block); 0835 KoParagraphStyle pStyle(block.blockFormat(), block.charFormat()); 0836 int dropCapsAffectsNMoreLines = 0; 0837 qreal dropCapsPositionAdjust = 0.0; 0838 bool lastOfPreviousRun = (d->copyEndOfArea && d->copyEndOfArea->it.currentBlock() == block); 0839 0840 KoText::Direction dir = pStyle.textProgressionDirection(); 0841 if (dir == KoText::InheritDirection) 0842 dir = parentTextDirection(); 0843 if (dir == KoText::AutoDirection) 0844 d->isRtl = block.text().isRightToLeft(); 0845 else 0846 d->isRtl = dir == KoText::RightLeftTopBottom; 0847 0848 // initialize list item stuff for this parag. 0849 QTextList *textList = block.textList(); 0850 QTextListFormat listFormat; 0851 QTextCharFormat labelFormat; 0852 if (textList) { 0853 listFormat = textList->format(); 0854 0855 if (block.text().size() == 0 || d->documentLayout->wordprocessingMode()) { 0856 labelFormat = block.charFormat(); 0857 } else { 0858 labelFormat = block.begin().fragment().charFormat(); 0859 } 0860 0861 if (d->documentLayout->styleManager()) { 0862 const int id = listFormat.intProperty(KoListStyle::CharacterStyleId); 0863 KoCharacterStyle *cs = d->documentLayout->styleManager()->characterStyle(id); 0864 if (cs) { 0865 cs->applyStyle(labelFormat); 0866 cs->ensureMinimalProperties(labelFormat); 0867 } 0868 } 0869 0870 // fetch the text-properties of the label 0871 if (listFormat.hasProperty(KoListStyle::CharacterProperties)) { 0872 QVariant v = listFormat.property(KoListStyle::CharacterProperties); 0873 QSharedPointer<KoCharacterStyle> textPropertiesCharStyle = v.value< QSharedPointer<KoCharacterStyle> >(); 0874 if (!textPropertiesCharStyle.isNull()) { 0875 textPropertiesCharStyle->applyStyle(labelFormat); 0876 textPropertiesCharStyle->ensureMinimalProperties(labelFormat); 0877 } 0878 } 0879 0880 // Calculate the correct font point size taking into account the current 0881 // block format and the relative font size percent if the size is not absolute 0882 if (listFormat.hasProperty(KoListStyle::RelativeBulletSize)) { 0883 qreal percent = listFormat.property(KoListStyle::RelativeBulletSize).toDouble(); 0884 labelFormat.setFontPointSize((percent*labelFormat.fontPointSize())/100.00); 0885 } 0886 0887 QFont font(labelFormat.font(), d->documentLayout->paintDevice()); 0888 0889 if (!blockData.hasCounterData()) { 0890 ListItemsHelper lih(textList, font); 0891 lih.recalculateBlock(block); 0892 } 0893 blockData.setLabelFormat(labelFormat); 0894 } else { // make sure it is empty 0895 blockData.clearCounter(); 0896 } 0897 0898 QTextLayout *layout = block.layout(); 0899 QTextOption option = layout->textOption(); 0900 option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 0901 0902 option.setAlignment(QStyle::visualAlignment(d->isRtl ? Qt::RightToLeft : Qt::LeftToRight, pStyle.alignment())); 0903 if (d->isRtl) { 0904 option.setTextDirection(Qt::RightToLeft); 0905 // For right-to-left we need to make sure that trailing spaces are included into the QTextLine naturalTextWidth 0906 // and naturalTextRect calculation so they are proper handled in the RunAroundHelper. For left-to-right we do 0907 // not like to include trailing spaces in the calculations cause else justified text would not look proper 0908 // justified. Seems for right-to-left we have to accept that justified text will not look proper justified then. 0909 // only set it for justified text as otherwise we will cut of text at the beginning of the line 0910 if (pStyle.alignment() == Qt::AlignJustify) { 0911 option.setFlags(QTextOption::IncludeTrailingSpaces); 0912 } 0913 0914 } else { 0915 option.setFlags(0); 0916 option.setTextDirection(Qt::LeftToRight); 0917 } 0918 0919 option.setUseDesignMetrics(true); 0920 0921 //========== 0922 // Drop caps 0923 //========== 0924 0925 d->dropCapsNChars = 0; 0926 if (cursor->lineTextStart == -1) { 0927 // first remove any drop-caps related formatting that's already there in the layout. 0928 // we'll do it all afresh now. 0929 QList<QTextLayout::FormatRange> formatRanges = layout->additionalFormats(); 0930 for (QList< QTextLayout::FormatRange >::Iterator iter = formatRanges.begin(); 0931 iter != formatRanges.end(); ) { 0932 if (iter->format.boolProperty(DropCapsAdditionalFormattingId)) { 0933 iter = formatRanges.erase(iter); 0934 } else { 0935 ++iter; 0936 } 0937 } 0938 if (formatRanges.count() != layout->additionalFormats().count()) 0939 layout->setAdditionalFormats(formatRanges); 0940 bool dropCaps = pStyle.dropCaps(); 0941 int dropCapsLength = pStyle.dropCapsLength(); 0942 int dropCapsLines = pStyle.dropCapsLines(); 0943 0944 if (dropCaps && dropCapsLines > 1 && block.length() > 1) { 0945 QString blockText = block.text(); 0946 d->dropCapsDistance = pStyle.dropCapsDistance(); 0947 0948 if (dropCapsLength == 0) { // means whole word is to be dropped 0949 int firstNonSpace = blockText.indexOf(QRegExp("[^ ]")); 0950 dropCapsLength = blockText.indexOf(QRegExp("\\W"), firstNonSpace); 0951 } else { 0952 // LibreOffice skips softbreaks but not spaces. We will do the same 0953 QTextCursor c1(block); 0954 c1.setPosition(block.position()); 0955 c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); 0956 0957 KoTextSoftPageBreak *softPageBreak = dynamic_cast<KoTextSoftPageBreak*>(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); 0958 if (softPageBreak) { 0959 dropCapsLength++; 0960 } 0961 } 0962 dropCapsLength = qMin(dropCapsLength, blockText.length() - 1); 0963 0964 if (dropCapsLength > 0) { 0965 // increase the size of the dropped chars 0966 QTextCursor blockStart(block); 0967 QTextLayout::FormatRange dropCapsFormatRange; 0968 dropCapsFormatRange.format = blockStart.charFormat(); 0969 0970 // find out lineHeight for this block. 0971 QTextBlock::iterator it = block.begin(); 0972 QTextFragment lineRepresentative = it.fragment(); 0973 qreal lineHeight = pStyle.lineHeightAbsolute(); 0974 qreal dropCapsHeight = 0; 0975 if (lineHeight == 0) { 0976 lineHeight = lineRepresentative.charFormat().fontPointSize(); 0977 qreal linespacing = pStyle.lineSpacing(); 0978 if (linespacing == 0) { // unset 0979 qreal percent = pStyle.lineHeightPercent(); 0980 if (percent != 0) 0981 linespacing = lineHeight * ((percent - 100) / 100.0); 0982 else if (linespacing == 0) 0983 linespacing = lineHeight * 0.2; // default 0984 } 0985 dropCapsHeight = linespacing * (dropCapsLines-1); 0986 } 0987 const qreal minimum = pStyle.minimumLineHeight(); 0988 if (minimum > 0.0) { 0989 lineHeight = qMax(lineHeight, minimum); 0990 } 0991 0992 dropCapsHeight += lineHeight * dropCapsLines; 0993 0994 int dropCapsStyleId = pStyle.dropCapsTextStyleId(); 0995 KoCharacterStyle *dropCapsCharStyle = 0; 0996 if (dropCapsStyleId > 0 && d->documentLayout->styleManager()) { 0997 dropCapsCharStyle = d->documentLayout->styleManager()->characterStyle(dropCapsStyleId); 0998 dropCapsCharStyle->applyStyle(dropCapsFormatRange.format); 0999 } 1000 1001 QFont f(dropCapsFormatRange.format.font(), d->documentLayout->paintDevice()); 1002 QString dropCapsText(block.text().left(dropCapsLength)); 1003 f.setPointSizeF(dropCapsHeight); 1004 for (int i=0; i < 5; ++i) { 1005 QTextLayout tmplayout(dropCapsText, f); 1006 tmplayout.setTextOption(option); 1007 tmplayout.beginLayout(); 1008 QTextLine tmpline = tmplayout.createLine(); 1009 tmplayout.endLayout(); 1010 d->dropCapsWidth = tmpline.naturalTextWidth(); 1011 1012 QFontMetricsF fm(f, documentLayout()->paintDevice()); 1013 QRectF rect = fm.tightBoundingRect(dropCapsText); 1014 const qreal diff = dropCapsHeight - rect.height(); 1015 dropCapsPositionAdjust = rect.top() + fm.ascent(); 1016 if (qAbs(diff) < 0.5) // good enough 1017 break; 1018 1019 const qreal adjustment = diff * (f.pointSizeF() / rect.height()); 1020 // warnTextLayout << "adjusting with" << adjustment; 1021 f.setPointSizeF(f.pointSizeF() + adjustment); 1022 } 1023 1024 dropCapsFormatRange.format.setFontPointSize(f.pointSizeF()); 1025 dropCapsFormatRange.format.setProperty(DropCapsAdditionalFormattingId, 1026 (QVariant) true); 1027 dropCapsFormatRange.start = 0; 1028 dropCapsFormatRange.length = dropCapsLength; 1029 formatRanges.append(dropCapsFormatRange); 1030 layout->setAdditionalFormats(formatRanges); 1031 1032 d->dropCapsNChars = dropCapsLength; 1033 dropCapsAffectsNMoreLines = (d->dropCapsNChars > 0) ? dropCapsLines : 0; 1034 } 1035 } 1036 } 1037 1038 //======== 1039 // Margins 1040 //======== 1041 qreal startMargin = block.blockFormat().leftMargin(); 1042 qreal endMargin = block.blockFormat().rightMargin(); 1043 if (d->isRtl) { 1044 qSwap(startMargin, endMargin); 1045 } 1046 d->indent = textIndent(block, textList, pStyle) + d->extraTextIndent; 1047 1048 qreal labelBoxWidth = 0; 1049 qreal labelBoxIndent = 0; 1050 if (textList) { 1051 if (listFormat.boolProperty(KoListStyle::AlignmentMode)) { 1052 // according to odf 1.2 17.20 list margin should be used when paragraph margin is 1053 // not specified by the auto style (additionally LO/OO uses 0 as condition so we do too) 1054 int id = pStyle.styleId(); 1055 bool set = false; 1056 if (id && d->documentLayout->styleManager()) { 1057 KoParagraphStyle *originalParagraphStyle = d->documentLayout->styleManager()->paragraphStyle(id); 1058 if (originalParagraphStyle->leftMargin() != startMargin) { 1059 set = (startMargin != 0); 1060 } 1061 } else { 1062 set = (startMargin != 0); 1063 } 1064 if (! set) { 1065 startMargin = listFormat.doubleProperty(KoListStyle::Margin); 1066 } 1067 1068 labelBoxWidth = blockData.counterWidth(); 1069 Qt::Alignment align = static_cast<Qt::Alignment>(listFormat.intProperty(KoListStyle::Alignment)); 1070 if (align == 0) { 1071 align = Qt::AlignLeft; 1072 } 1073 if (align & Qt::AlignLeft) { 1074 d->indent += labelBoxWidth; 1075 } else if (align & Qt::AlignHCenter) { 1076 d->indent += labelBoxWidth/2; 1077 } 1078 labelBoxIndent = d->indent - labelBoxWidth; 1079 } else { 1080 labelBoxWidth = blockData.counterSpacing() + blockData.counterWidth(); 1081 } 1082 } 1083 1084 d->width = right() - left(); 1085 d->width -= startMargin + endMargin; 1086 d->x = left() + (d->isRtl ? 0.0 : startMargin); 1087 1088 d->documentLayout->clearInlineObjectRegistry(block); 1089 1090 //======== 1091 // Tabs 1092 //======== 1093 const QVector<KoText::Tab> tabs = pStyle.tabPositions(); 1094 1095 // Handle tabs relative to startMargin 1096 qreal tabOffset = -d->indent; 1097 if (!d->documentLayout->relativeTabs(block)) { 1098 tabOffset -= startMargin; 1099 } 1100 1101 // Make a list of tabs that Qt can use 1102 QList<QTextOption::Tab> qTabs; 1103 // Note: Converting to Qt tabs is needed as long as we use Qt for layout, but we 1104 // loose the possibility to do leader chars. 1105 foreach (const KoText::Tab &kTab, tabs) { 1106 qreal value = kTab.position; 1107 if (value == MaximumTabPos) { // MaximumTabPos is used in index generators 1108 // note: we subtract right margin as this is where the tab should be 1109 // note: we subtract indent so tab is not relative to it 1110 // note: we subtract left margin so tab is not relative to it 1111 // if rtl the above left/right reasons swap but formula stays the same 1112 // -tabOfset is just to cancel that we add it next 1113 // -2 is to avoid wrap at right edge to the next line 1114 value = right() - left() - startMargin - endMargin - d->indent - tabOffset - 2; 1115 } 1116 1117 // conversion here is required because Qt thinks in device units and we don't 1118 value *= qt_defaultDpiY() / 72.0; 1119 1120 value += tabOffset * qt_defaultDpiY() / 72.0; 1121 1122 QTextOption::Tab tab; 1123 tab.position = value; 1124 tab.type = kTab.type; 1125 tab.delimiter = kTab.delimiter; 1126 qTabs.append(tab); 1127 } 1128 1129 qreal presentationListTabValue(0.0); // for use in presentationListTabWorkaround 1130 1131 // For some lists we need to add a special list tab according to odf 1.2 19.830 1132 if (textList && listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::ListTab) { 1133 qreal listTab = 0; 1134 if (listFormat.hasProperty(KoListStyle::TabStopPosition)) { 1135 listTab = listFormat.doubleProperty(KoListStyle::TabStopPosition); 1136 if (!d->documentLayout->relativeTabs(block)) { 1137 // How list tab is defined if fixed tabs: 1138 // listTab 1139 //|>-------------------------| 1140 // d->indent 1141 // |---------<| 1142 // LABEL TEXT STARTS HERE AND GOES ON 1143 // TO THE NEXT LINE 1144 //|>------------------| 1145 // startMargin 1146 listTab -= startMargin; 1147 } else { 1148 // How list tab is defined if relative tabs: 1149 // It's relative to startMargin - list.startMargin 1150 // listTab 1151 // |>-------------------| 1152 // d->indent 1153 // |---------<| 1154 // LABEL TEXT STARTS HERE AND GOES ON 1155 // TO THE NEXT LINE 1156 //|>--------------------| 1157 // startMargin | 1158 // |>-------------| 1159 // list.margin 1160 listTab -= listFormat.doubleProperty(KoListStyle::Margin); 1161 } 1162 } 1163 // How list tab is defined now: 1164 // listTab 1165 // |>-----| 1166 // d->indent 1167 // |---------<| 1168 // LABEL TEXT STARTS HERE AND GOES ON 1169 // TO THE NEXT LINE 1170 //|>------------------| 1171 // startMargin 1172 presentationListTabValue = listTab; 1173 listTab -= d->indent; 1174 1175 // And now listTab is like this: 1176 // x() 1177 // | listTab 1178 // |>---------------| 1179 // d->indent 1180 // |---------<| 1181 // LABEL TEXT STARTS HERE AND GOES ON 1182 // TO THE NEXT LINE 1183 //|>------------------| 1184 // startMargin 1185 1186 // conversion here is required because Qt thinks in device units and we don't 1187 listTab *= qt_defaultDpiY() / 72.0; 1188 1189 QTextOption::Tab tab; 1190 tab.position = listTab; 1191 tab.type = d->isRtl ? QTextOption::RightTab : QTextOption::LeftTab; 1192 qTabs.append(tab); 1193 } 1194 1195 // We need to sort as the MaximumTabPos may be converted to a value that really 1196 // should be in the middle, and listtab needs to be sorted in too 1197 std::sort(qTabs.begin(), qTabs.end(), compareTab); 1198 1199 // Regular interval tabs. Since Qt doesn't handle regular interval tabs offset 1200 // by a fixed number we need to create the regular tabs ourselves. 1201 qreal tabStopDistance = pStyle.tabStopDistance() * qt_defaultDpiY() / 72.0; 1202 if (tabStopDistance <= 0) { 1203 tabStopDistance = d->documentLayout->defaultTabSpacing() * qt_defaultDpiY() / 72.0; 1204 } 1205 1206 qreal regularSpacedTabPos = -d->indent * qt_defaultDpiY() / 72.0 -0.1; // first possible position 1207 if (!qTabs.isEmpty()) { 1208 regularSpacedTabPos = qTabs.last().position; 1209 } 1210 1211 regularSpacedTabPos -= tabOffset * qt_defaultDpiY() / 72.0; 1212 if (regularSpacedTabPos < 0) { 1213 regularSpacedTabPos = -int(-regularSpacedTabPos / tabStopDistance) * tabStopDistance; 1214 } else { 1215 regularSpacedTabPos = (int(regularSpacedTabPos / tabStopDistance) + 1) * tabStopDistance; 1216 } 1217 regularSpacedTabPos += tabOffset * qt_defaultDpiY() / 72.0; 1218 1219 while (regularSpacedTabPos < MaximumTabPos) { 1220 QTextOption::Tab tab; 1221 tab.position = regularSpacedTabPos; 1222 qTabs.append(tab); 1223 regularSpacedTabPos += tabStopDistance; 1224 } 1225 1226 option.setTabs(qTabs); 1227 1228 // conversion here is required because Qt thinks in device units and we don't 1229 option.setTabStop(tabStopDistance * qt_defaultDpiY() / 72.); 1230 1231 layout->setTextOption(option); 1232 1233 1234 // ============== 1235 // Possibly store the old layout of lines in case we end up splitting the paragraph at the same position 1236 // ============== 1237 QVector<LineKeeper> stashedLines; 1238 QPointF stashedCounterPosition; 1239 if (lastOfPreviousRun) { 1240 // we have been layouted before, and the block ended on the following page so better 1241 // stash the layout for later 1242 d->stashRemainingLayout(block, d->copyEndOfArea->lineTextStart, stashedLines, stashedCounterPosition); 1243 } 1244 1245 // ============== 1246 // Setup line and possibly restart paragraph continuing from previous other area 1247 // ============== 1248 QTextLine line; 1249 if (cursor->lineTextStart == -1) { 1250 layout->beginLayout(); 1251 line = layout->createLine(); 1252 cursor->fragmentIterator = block.begin(); 1253 } else { 1254 line = d->restartLayout(block, cursor->lineTextStart); 1255 d->indent = d->extraTextIndent; 1256 } 1257 1258 if (block.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { 1259 // Unnumbered list items act like "following lines" in a numbered block 1260 d->indent = 0; 1261 } 1262 1263 // ============== 1264 // List label/counter positioning 1265 // ============== 1266 if (textList && block.layout()->lineCount() == 1 1267 && ! block.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { 1268 // If first line in a list then set the counterposition. Following lines in the same 1269 // list-item have nothing to do with the counter. 1270 if (listFormat.boolProperty(KoListStyle::AlignmentMode) == false) { 1271 qreal minLabelWidth = listFormat.doubleProperty(KoListStyle::MinimumWidth); 1272 if (!d->isRtl) { 1273 d->x += listFormat.doubleProperty(KoListStyle::Indent) + minLabelWidth; 1274 } 1275 d->width -= listFormat.doubleProperty(KoListStyle::Indent) + minLabelWidth; 1276 d->indent += labelBoxWidth - minLabelWidth; 1277 blockData.setCounterPosition(QPointF(d->x + d->indent - labelBoxWidth, d->y)); 1278 } else if (labelBoxWidth > 0.0 || blockData.counterText().length() > 0) { 1279 // Alignmentmode and there is a label (double check needed to account for both 1280 // picture bullets and non width chars) 1281 blockData.setCounterPosition(QPointF(d->x + labelBoxIndent, d->y)); 1282 if (listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::ListTab 1283 && !presentationListTabWorkaround(textIndent(block, textList, pStyle), labelBoxWidth, presentationListTabValue)) { 1284 foreach(QTextOption::Tab tab, qTabs) { 1285 qreal position = tab.position * 72. / qt_defaultDpiY(); 1286 if (position > 0.0) { 1287 d->indent += position; 1288 break; 1289 } 1290 } 1291 1292 //And finally it's like this: 1293 // x() 1294 // d->indent 1295 // |>-----| 1296 // LABEL TEXT STARTS HERE AND GOES ON 1297 // TO THE NEXT LINE 1298 //|>------------------| 1299 // startMargin 1300 } else if (listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::Space) { 1301 QFontMetrics fm(labelFormat.font(), d->documentLayout->paintDevice()); 1302 d->indent += fm.width(' '); 1303 } 1304 // default needs to be no space so presentationListTabWorkaround above makes us go here 1305 } 1306 } 1307 1308 // Whenever we relayout the markup layout becomes invalid 1309 blockData.setMarkupsLayoutValidity(KoTextBlockData::Misspell, false); 1310 blockData.setMarkupsLayoutValidity(KoTextBlockData::Grammar, false); 1311 1312 // ============== 1313 // Now once we know the physical context we can work on the borders of the paragraph 1314 // ============== 1315 if (block.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { 1316 if (!d->blockRects.isEmpty()) { 1317 d->blockRects.last().setBottom(d->y); 1318 } 1319 d->y += d->bottomSpacing; 1320 d->bottomSpacing = 0; 1321 d->blockRects.append(QRectF(d->x, d->y, d->width, 10.0)); 1322 } else { 1323 handleBordersAndSpacing(blockData, &block); 1324 } 1325 1326 // Expand bounding rect so if we have content outside we show it 1327 expandBoundingLeft(d->blockRects.last().x()); 1328 expandBoundingRight(d->blockRects.last().right()); 1329 1330 // ============== 1331 // Create the lines of this paragraph 1332 // ============== 1333 RunAroundHelper runAroundHelper; 1334 runAroundHelper.setObstructions(documentLayout()->currentObstructions()); 1335 qreal maxLineHeight = 0; 1336 qreal y_justBelowDropCaps = 0; 1337 bool anyLineAdded = false; 1338 int numBaselineShifts = 0; 1339 1340 while (line.isValid()) { 1341 runAroundHelper.setLine(this, line); 1342 runAroundHelper.setObstructions(documentLayout()->currentObstructions()); 1343 QRectF anchoringRect = d->blockRects.last(); 1344 anchoringRect.setTop(d->anchoringParagraphContentTop); 1345 documentLayout()->setAnchoringParagraphContentRect(anchoringRect); 1346 anchoringRect.setLeft(left()); 1347 anchoringRect.setWidth(right() - left()); 1348 anchoringRect.setTop(d->anchoringParagraphTop); 1349 documentLayout()->setAnchoringParagraphRect(anchoringRect); 1350 documentLayout()->setAnchoringLayoutEnvironmentRect(layoutEnvironmentRect()); 1351 runAroundHelper.fit( /* resetHorizontalPosition */ false, /* rightToLeft */ d->isRtl, QPointF(x(), d->y)); 1352 1353 documentLayout()->positionAnchorTextRanges(block.position()+line.textStart(), line.textLength(), block.document()); 1354 qreal bottomOfText = line.y() + line.height(); 1355 1356 bool softBreak = false; 1357 bool moreInMiddle = d->y > maximumAllowedBottom() - 150; 1358 if (acceptsPageBreak() && !pStyle.nonBreakableLines() && moreInMiddle) { 1359 int softBreakPos = -1; 1360 QString text = block.text(); 1361 int pos = text.indexOf(QChar::ObjectReplacementCharacter, line.textStart()); 1362 1363 while (pos >= 0 && pos <= line.textStart() + line.textLength()) { 1364 QTextCursor c1(block); 1365 c1.setPosition(block.position() + pos); 1366 c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); 1367 1368 KoTextSoftPageBreak *softPageBreak = dynamic_cast<KoTextSoftPageBreak*>(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); 1369 if (softPageBreak) { 1370 softBreakPos = pos; 1371 break; 1372 } 1373 1374 pos = text.indexOf(QChar::ObjectReplacementCharacter, pos + 1); 1375 } 1376 1377 if (softBreakPos >= 0 && softBreakPos < line.textStart() + line.textLength()) { 1378 line.setNumColumns(softBreakPos - line.textStart() + 1, line.width()); 1379 softBreak = true; 1380 // if the softBreakPos is at the start of the line stop here so 1381 // we don't add a line here. That fixes the problem that e.g. the counter is before 1382 // the page break and the text is after the page break 1383 if (!virginPage() && softBreakPos == 0) { 1384 d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); 1385 layout->endLayout(); 1386 return false; 1387 } 1388 } 1389 } 1390 1391 if (documentLayout()->anchoringSoftBreak() <= block.position() + line.textStart() + line.textLength()) { 1392 //don't add an anchor that has been moved away 1393 line.setNumColumns(documentLayout()->anchoringSoftBreak() - block.position() - line.textStart(), line.width()); 1394 softBreak = true; 1395 // if the softBreakPos is at the start of the block stop here so 1396 // we don't add a line here. That fixes the problem that e.g. the counter is before 1397 // the page break and the text is after the page break 1398 if (!virginPage() && documentLayout()->anchoringSoftBreak() == block.position()) { 1399 d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); 1400 layout->endLayout(); 1401 return false; 1402 } 1403 } 1404 1405 findFootNotes(block, line, bottomOfText); 1406 if (bottomOfText > maximumAllowedBottom()) { 1407 // We can not fit line within our allowed space 1408 // in case we resume layout on next page the line is reused later 1409 // but if not then we need to make sure the line becomes invisible 1410 // we use d->maximalAllowedBottom because we want to be below 1411 // footnotes too. 1412 if (!virginPage() && pStyle.nonBreakableLines()) { 1413 line.setPosition(QPointF(x(), d->maximalAllowedBottom)); 1414 cursor->lineTextStart = -1; 1415 d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); 1416 layout->endLayout(); 1417 clearPreregisteredFootNotes(); 1418 return false; //to indicate block was not done! 1419 } 1420 if (!virginPage() && pStyle.orphanThreshold() != 0 1421 && pStyle.orphanThreshold() > numBaselineShifts) { 1422 line.setPosition(QPointF(x(), d->maximalAllowedBottom)); 1423 cursor->lineTextStart = -1; 1424 d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); 1425 layout->endLayout(); 1426 clearPreregisteredFootNotes(); 1427 return false; //to indicate block was not done! 1428 } 1429 if (!virginPage() || anyLineAdded) { 1430 line.setPosition(QPointF(x(), d->maximalAllowedBottom)); 1431 d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); 1432 layout->endLayout(); 1433 clearPreregisteredFootNotes(); 1434 return false; //to indicate block was not done! 1435 } 1436 } 1437 confirmFootNotes(); 1438 anyLineAdded = true; 1439 maxLineHeight = qMax(maxLineHeight, addLine(line, cursor, blockData)); 1440 1441 d->neededWidth = qMax(d->neededWidth, line.naturalTextWidth() + d->indent); 1442 1443 if (!runAroundHelper.stayOnBaseline() && !(block.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable) 1444 && block.length() <= 1)) { 1445 d->y += maxLineHeight; 1446 maxLineHeight = 0; 1447 d->indent = 0; 1448 d->extraTextIndent = 0; 1449 ++numBaselineShifts; 1450 } 1451 1452 // drop caps 1453 if (d->dropCapsNChars > 0) { // we just laid out the dropped chars 1454 y_justBelowDropCaps = d->y; // save the y position just below the dropped characters 1455 d->y = line.y(); // keep the same y for the next line 1456 line.setPosition(line.position() - QPointF(0, dropCapsPositionAdjust)); 1457 d->dropCapsNChars -= line.textLength(); 1458 } else if (dropCapsAffectsNMoreLines > 0) { // we just laid out a drop-cap-affected line 1459 dropCapsAffectsNMoreLines--; 1460 if (dropCapsAffectsNMoreLines == 0) { // no more drop-cap-affected lines 1461 if (d->y < y_justBelowDropCaps) 1462 d->y = y_justBelowDropCaps; // make sure d->y is below the dropped characters 1463 y_justBelowDropCaps = 0; 1464 d->dropCapsWidth = 0; 1465 d->dropCapsDistance = 0; 1466 } 1467 } 1468 documentLayout()->positionAnchoredObstructions(); 1469 1470 // line fitted so try and do the next one 1471 line = layout->createLine(); 1472 if (!line.isValid()) { 1473 break; // no more line means our job is done 1474 } 1475 cursor->lineTextStart = line.textStart(); 1476 1477 if (softBreak) { 1478 d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); 1479 layout->endLayout(); 1480 return false; // page-break means we need to start again on the next page 1481 } 1482 } 1483 1484 d->bottomSpacing = pStyle.bottomMargin(); 1485 1486 layout->endLayout(); 1487 setVirginPage(false); 1488 cursor->lineTextStart = -1; //set lineTextStart to -1 and returning true indicate new block 1489 block.setLineCount(layout->lineCount()); 1490 return true; 1491 } 1492 1493 bool KoTextLayoutArea::presentationListTabWorkaround(qreal indent, qreal labelBoxWidth, qreal presentationListTabValue) 1494 { 1495 if (!d->documentLayout->wordprocessingMode() && indent < 0.0) { 1496 // Impress / Powerpoint expects the label to be before the text 1497 if (indent + labelBoxWidth >= presentationListTabValue) { 1498 // but here is an unforseen overlap with normal text 1499 return true; 1500 } 1501 } 1502 return false; 1503 } 1504 1505 qreal KoTextLayoutArea::textIndent(const QTextBlock &block, QTextList *textList, const KoParagraphStyle &pStyle) const 1506 { 1507 if (pStyle.autoTextIndent()) { 1508 // if auto-text-indent is set, 1509 // return an indent approximately 3-characters wide as per current font 1510 QTextCursor blockCursor(block); 1511 qreal guessGlyphWidth = QFontMetricsF(blockCursor.charFormat().font()).width('x'); 1512 return guessGlyphWidth * 3; 1513 } 1514 1515 qreal blockTextIndent = block.blockFormat().textIndent(); 1516 1517 if (textList && textList->format().boolProperty(KoListStyle::AlignmentMode)) { 1518 // according to odf 1.2 17.20 list text indent should be used when paragraph text indent is 1519 // not specified (additionally LO/OO uses 0 as condition so we do too) 1520 int id = pStyle.styleId(); 1521 bool set = false; 1522 if (id && d->documentLayout->styleManager()) { 1523 KoParagraphStyle *originalParagraphStyle = d->documentLayout->styleManager()->paragraphStyle(id); 1524 if (originalParagraphStyle->textIndent() != blockTextIndent) { 1525 set = (blockTextIndent != 0); 1526 } 1527 } else { 1528 set = (blockTextIndent != 0); 1529 } 1530 if (! set) { 1531 return textList->format().doubleProperty(KoListStyle::TextIndent); 1532 } 1533 } 1534 return blockTextIndent; 1535 } 1536 1537 void KoTextLayoutArea::setExtraTextIndent(qreal extraTextIndent) 1538 { 1539 d->extraTextIndent = extraTextIndent; 1540 } 1541 1542 qreal KoTextLayoutArea::x() const 1543 { 1544 if (d->isRtl) { 1545 return d->x; 1546 } else { 1547 if (d->dropCapsNChars > 0 || d->dropCapsWidth == 0) 1548 return d->x + d->indent ; 1549 else 1550 return d->x + d->indent + d->dropCapsWidth + d->dropCapsDistance; 1551 } 1552 } 1553 1554 qreal KoTextLayoutArea::width() const 1555 { 1556 if (d->dropCapsNChars > 0) { 1557 return d->dropCapsWidth; 1558 } 1559 qreal width = d->width; 1560 if (d->maximumAllowedWidth > 0) { 1561 // lets use that instead but remember all the indent stuff we have calculated 1562 width = d->width - (d->right - d->left) + d->maximumAllowedWidth; 1563 } 1564 return width - d->indent - d->dropCapsWidth - d->dropCapsDistance; 1565 } 1566 1567 void KoTextLayoutArea::setAcceptsPageBreak(bool accept) 1568 { 1569 d->acceptsPageBreak = accept; 1570 } 1571 1572 bool KoTextLayoutArea::acceptsPageBreak() const 1573 { 1574 return d->acceptsPageBreak; 1575 } 1576 1577 void KoTextLayoutArea::setAcceptsColumnBreak(bool accept) 1578 { 1579 d->acceptsColumnBreak = accept; 1580 } 1581 1582 bool KoTextLayoutArea::acceptsColumnBreak() const 1583 { 1584 return d->acceptsColumnBreak; 1585 } 1586 1587 void KoTextLayoutArea::setVirginPage(bool virgin) 1588 { 1589 d->virginPage = virgin; 1590 } 1591 1592 bool KoTextLayoutArea::virginPage() const 1593 { 1594 return d->virginPage; 1595 } 1596 1597 void KoTextLayoutArea::setVerticalAlignOffset(qreal offset) 1598 { 1599 d->boundingRect.setTop(d->top + qMin(qreal(0.0), offset)); 1600 d->boundingRect.setBottom(d->bottom + qMax(qreal(0.0), offset)); 1601 Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom(), __FUNCTION__, "Bounding-rect is not normalized"); 1602 d->verticalAlignOffset = offset; 1603 } 1604 1605 qreal KoTextLayoutArea::verticalAlignOffset() const 1606 { 1607 return d->verticalAlignOffset; 1608 } 1609 1610 qreal KoTextLayoutArea::addLine(QTextLine &line, FrameIterator *cursor, KoTextBlockData &blockData) 1611 { 1612 QTextBlock block = cursor->it.currentBlock(); 1613 QTextBlockFormat format = block.blockFormat(); 1614 KoParagraphStyle style(format, block.charFormat()); 1615 1616 if (block.textList() && block.layout()->lineCount() == 1) { 1617 Qt::Alignment alignment = format.alignment(); 1618 if (d->isRtl && (alignment & Qt::AlignAbsolute) == 0) { 1619 if (alignment & Qt::AlignLeft) { 1620 alignment = Qt::AlignRight; 1621 } else if (alignment & Qt::AlignRight) { 1622 alignment = Qt::AlignLeft; 1623 } 1624 } 1625 alignment &= Qt::AlignRight | Qt::AlignLeft | Qt::AlignHCenter; 1626 1627 // First line, lets check where the line ended up and adjust the positioning of the counter. 1628 qreal newX; 1629 if (alignment & Qt::AlignHCenter) { 1630 const qreal padding = (line.width() - line.naturalTextWidth()) / 2; 1631 newX = blockData.counterPosition().x() + (d->isRtl ? -padding : padding); 1632 } else if (alignment & Qt::AlignRight) { 1633 const qreal padding = line.width() - line.naturalTextWidth(); 1634 newX = blockData.counterPosition().x() + (d->isRtl ? -padding : padding); 1635 } else { 1636 newX = blockData.counterPosition().x(); 1637 } 1638 if (d->isRtl) { 1639 newX = line.x() + line.naturalTextWidth() + line.x() + d->indent - newX; 1640 } 1641 1642 blockData.setCounterPosition(QPointF(newX, blockData.counterPosition().y())); 1643 } 1644 1645 qreal height = 0; 1646 qreal breakHeight = 0.0; 1647 qreal ascent = 0.0; 1648 qreal descent = 0.0; 1649 const bool useFontProperties = format.boolProperty(KoParagraphStyle::LineSpacingFromFont); 1650 1651 if (cursor->fragmentIterator.atEnd()) {// no text in parag. 1652 qreal fontStretch = 1; 1653 QTextCharFormat charFormat = block.charFormat(); 1654 if (block.blockFormat().hasProperty(KoParagraphStyle::EndCharStyle)) { 1655 QVariant v = block.blockFormat().property(KoParagraphStyle::EndCharStyle); 1656 QSharedPointer<KoCharacterStyle> endCharStyle = v.value< QSharedPointer<KoCharacterStyle> >(); 1657 if (!endCharStyle.isNull()) { 1658 endCharStyle->applyStyle(charFormat); 1659 endCharStyle->ensureMinimalProperties(charFormat); 1660 } 1661 } 1662 1663 if (useFontProperties) { 1664 //stretch line height to powerpoint size 1665 fontStretch = PresenterFontStretch; 1666 } else if (block.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { 1667 // stretch line height to ms-word size 1668 fontStretch = charFormat.property(KoCharacterStyle::FontYStretch).toDouble(); 1669 } 1670 height = charFormat.fontPointSize() * fontStretch; 1671 } else { 1672 qreal fontStretch = 1; 1673 QTextFragment fragment = cursor->fragmentIterator.fragment(); 1674 if (useFontProperties) { 1675 //stretch line height to powerpoint size 1676 fontStretch = PresenterFontStretch; 1677 } else if (fragment.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { 1678 // stretch line height to ms-word size 1679 fontStretch = fragment.charFormat().property(KoCharacterStyle::FontYStretch).toDouble(); 1680 } 1681 // read max font height 1682 height = qMax(height, fragment.charFormat().fontPointSize() * fontStretch); 1683 1684 KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); 1685 ascent = qMax(ascent, pos.m_ascent); 1686 descent = qMax(descent, pos.m_descent); 1687 1688 bool lineBreak = false; 1689 int lastCharPos = block.position() + line.textStart() + line.textLength() - 1; 1690 int blockLastCharWithoutPreedit = line.textStart() + line.textLength() - 1; 1691 if (block.layout()->preeditAreaPosition() >= block.position() + line.textStart() && 1692 block.layout()->preeditAreaPosition() <= lastCharPos) { 1693 blockLastCharWithoutPreedit -= block.layout()->preeditAreaText().length(); 1694 } 1695 if (block.text().at(blockLastCharWithoutPreedit) == QChar(0x2028)) { 1696 // Was a line with line-break 1697 if (line.textLength() != 1) { //unless empty line we should ignore the format of it 1698 --lastCharPos; 1699 } 1700 lineBreak = true; 1701 } 1702 while (!(fragment.contains(lastCharPos))) { 1703 cursor->fragmentIterator++; 1704 if (cursor->fragmentIterator.atEnd()) { 1705 break; 1706 } 1707 fragment = cursor->fragmentIterator.fragment(); 1708 if (!d->documentLayout->changeTracker() 1709 || !d->documentLayout->changeTracker()->displayChanges() 1710 || !d->documentLayout->changeTracker()->containsInlineChanges(fragment.charFormat()) 1711 || !d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt()) 1712 || !d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt())->isEnabled() 1713 || (d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt())->getChangeType() != KoGenChange::DeleteChange) 1714 || d->documentLayout->changeTracker()->displayChanges()) { 1715 qreal fontStretch = 1; 1716 if (useFontProperties) { 1717 //stretch line height to powerpoint size 1718 fontStretch = PresenterFontStretch; 1719 } else if (fragment.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { 1720 // stretch line height to ms-word size 1721 fontStretch = fragment.charFormat().property(KoCharacterStyle::FontYStretch).toDouble(); 1722 } 1723 // read max font height 1724 height = qMax(height, fragment.charFormat().fontPointSize() * fontStretch); 1725 1726 KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); 1727 ascent = qMax(ascent, pos.m_ascent); 1728 descent = qMax(descent, pos.m_descent); 1729 } 1730 } 1731 1732 if (lineBreak) { 1733 // Was a line with line-break - the format of the line-break should not be 1734 // considered for the next line either. So we may have to advance the fragmentIterator. 1735 while (!cursor->fragmentIterator.atEnd() && lastCharPos > fragment.position() + fragment.length()-1) { 1736 cursor->fragmentIterator++; 1737 fragment = cursor->fragmentIterator.fragment(); 1738 } 1739 1740 qreal breakAscent = ascent; 1741 qreal breakDescent = descent; 1742 breakHeight = height; 1743 1744 int firstPos = block.position() + line.textStart() + line.textLength(); 1745 1746 // Was a line with line-break - the format of the line-break should not be 1747 // considered for the next line either. So we may have to advance the fragmentIterator. 1748 while (!cursor->fragmentIterator.atEnd() && firstPos > fragment.position() + fragment.length()-1) { 1749 cursor->fragmentIterator++; 1750 if (!cursor->fragmentIterator.atEnd()) { 1751 fragment = cursor->fragmentIterator.fragment(); 1752 1753 // read max font height 1754 breakHeight = qMax(breakHeight, fragment.charFormat().fontPointSize() * fontStretch); 1755 1756 KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); 1757 breakAscent = qMax(breakAscent, pos.m_ascent); 1758 breakDescent = qMax(breakDescent, pos.m_descent); 1759 } 1760 } 1761 breakHeight = qMax(breakHeight, breakAscent + breakDescent); 1762 } 1763 } 1764 1765 height = qMax(height, ascent + descent); 1766 1767 if (height < 0.01) { 1768 height = 12; // default size for uninitialized styles. 1769 } 1770 1771 // Calculate adjustment to the height due to line height calculated by qt which shouldn't be 1772 // there in reality. We will just move the line 1773 qreal lineAdjust = 0.0; 1774 if (breakHeight > height) { 1775 lineAdjust = height - breakHeight; 1776 } 1777 1778 // Adjust the line-height according to a probably defined fixed line height, 1779 // a proportional (percent) line-height and/or the line-spacing. Together 1780 // with the line-height we maybe also need to adjust the position of the 1781 // line. This is for example needed if the line needs to shrink in height 1782 // so the line-text stays on the baseline. If the line grows in height then 1783 // we don't need to do anything. 1784 if (d->dropCapsNChars <= 0) { // linespacing rules doesn't apply to drop caps 1785 qreal fixedLineHeight = format.doubleProperty(KoParagraphStyle::FixedLineHeight); 1786 if (fixedLineHeight != 0.0) { 1787 qreal prevHeight = height; 1788 height = fixedLineHeight; 1789 lineAdjust += height - prevHeight; 1790 } else { 1791 qreal lineSpacing = format.doubleProperty(KoParagraphStyle::LineSpacing); 1792 if (lineSpacing == 0.0) { // unset 1793 qreal percent = format.doubleProperty(KoParagraphStyle::PercentLineHeight); 1794 if (percent != 0) { 1795 height *= percent / 100.0; 1796 } 1797 height *= 1.16; // default 1798 } 1799 height += lineSpacing; 1800 } 1801 1802 qreal minimum = style.minimumLineHeight(); 1803 if (minimum > 0.0) { 1804 height = qMax(height, minimum); 1805 } 1806 } else { 1807 // for drop caps we just work with a basic linespacing for the dropped characters 1808 height *= 1.16; 1809 } 1810 //rounding problems due to Qt-scribe internally using ints. 1811 //also used when line was moved down because of intersections with other shapes 1812 if (qAbs(d->y - line.y()) >= 0.126) { 1813 d->y = line.y(); 1814 } 1815 1816 if (lineAdjust) { 1817 // Adjust the position of the line itself. 1818 line.setPosition(QPointF(line.x(), line.y() + lineAdjust)); 1819 1820 // Adjust the position of the block-rect for this line which is used later 1821 // to proper clip the line while drawing. If we would not adjust it here 1822 // then we could end with text-lines being partly cutoff. 1823 if (lineAdjust < 0.0) { 1824 d->blockRects.last().moveTop(d->blockRects.last().top() + lineAdjust); 1825 } 1826 1827 if (block.textList() && block.layout()->lineCount() == 1) { 1828 // If this is the first line in a list (aka the first line after the list- 1829 // item) then we also need to adjust the counter to match to the line again. 1830 blockData.setCounterPosition(QPointF(blockData.counterPosition().x(), blockData.counterPosition().y() + lineAdjust)); 1831 } 1832 } 1833 1834 return height; 1835 } 1836 1837 void KoTextLayoutArea::setLayoutEnvironmentResctictions(bool isLayoutEnvironment, bool actsHorizontally) 1838 { 1839 d->isLayoutEnvironment = isLayoutEnvironment; 1840 d->actsHorizontally = actsHorizontally; 1841 } 1842 1843 QRectF KoTextLayoutArea::layoutEnvironmentRect() const 1844 { 1845 QRectF rect(-5e10, -5e10, 10e10, 10e20); // large values that never really restrict anything 1846 1847 if (d->parent) { 1848 rect = d->parent->layoutEnvironmentRect(); 1849 } 1850 1851 if (d->isLayoutEnvironment) { 1852 if (d->actsHorizontally) { 1853 rect.setLeft(left()); 1854 rect.setRight(right()); 1855 } 1856 rect.setTop(top()); 1857 rect.setBottom(maximumAllowedBottom()); 1858 } 1859 1860 return rect; 1861 } 1862 1863 QRectF KoTextLayoutArea::boundingRect() const 1864 { 1865 return d->boundingRect; 1866 } 1867 1868 qreal KoTextLayoutArea::maximumAllowedBottom() const 1869 { 1870 return d->maximalAllowedBottom - d->footNotesHeight 1871 - d->preregisteredFootNotesHeight; 1872 } 1873 1874 FrameIterator *KoTextLayoutArea::footNoteCursorToNext() const 1875 { 1876 return d->footNoteCursorToNext; 1877 } 1878 1879 KoInlineNote *KoTextLayoutArea::continuedNoteToNext() const 1880 { 1881 return d->continuedNoteToNext; 1882 } 1883 1884 int KoTextLayoutArea::footNoteAutoCount() const 1885 { 1886 return d->footNoteAutoCount; 1887 } 1888 1889 void KoTextLayoutArea::setFootNoteCountInDoc(int count) 1890 { 1891 d->footNoteCountInDoc = count; 1892 } 1893 1894 void KoTextLayoutArea::setFootNoteFromPrevious(FrameIterator *footNoteCursor, KoInlineNote *note) 1895 { 1896 d->footNoteCursorFromPrevious = footNoteCursor; 1897 d->continuedNoteFromPrevious = note; 1898 } 1899 1900 void KoTextLayoutArea::setNoWrap(qreal maximumAllowedWidth) 1901 { 1902 d->maximumAllowedWidth = maximumAllowedWidth; 1903 } 1904 1905 KoText::Direction KoTextLayoutArea::parentTextDirection() const 1906 { 1907 Q_ASSERT(d->parent); //Root areas should overload this method 1908 return d->parent->parentTextDirection(); 1909 } 1910 1911 KoTextLayoutArea *KoTextLayoutArea::parent() const 1912 { 1913 return d->parent; 1914 } 1915 1916 KoTextDocumentLayout *KoTextLayoutArea::documentLayout() const 1917 { 1918 return d->documentLayout; 1919 } 1920 1921 void KoTextLayoutArea::setReferenceRect(qreal left, qreal right, qreal top, qreal maximumAllowedBottom) 1922 { 1923 d->left = left; 1924 d->right = right; 1925 d->top = top; 1926 d->boundingRect = QRectF(left, top, right - left, 0.0); 1927 Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom() && d->boundingRect.left() <= d->boundingRect.right(), __FUNCTION__, "Bounding-rect is not normalized"); 1928 d->maximalAllowedBottom = maximumAllowedBottom; 1929 } 1930 1931 QRectF KoTextLayoutArea::referenceRect() const 1932 { 1933 return QRectF(d->left, d->top, d->right - d->left, d->bottom - d->top); 1934 } 1935 1936 qreal KoTextLayoutArea::left() const 1937 { 1938 return d->left; 1939 } 1940 1941 qreal KoTextLayoutArea::right() const 1942 { 1943 return d->right; 1944 } 1945 1946 qreal KoTextLayoutArea::top() const 1947 { 1948 return d->top; 1949 } 1950 1951 qreal KoTextLayoutArea::bottom() const 1952 { 1953 return d->bottom; 1954 } 1955 1956 void KoTextLayoutArea::setBottom(qreal bottom) 1957 { 1958 d->boundingRect.setBottom(bottom + qMax(qreal(0.0), d->verticalAlignOffset)); 1959 Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom(), __FUNCTION__, "Bounding-rect is not normalized"); 1960 d->bottom = bottom; 1961 } 1962 1963 void KoTextLayoutArea::findFootNotes(const QTextBlock &block, const QTextLine &line, qreal bottomOfText) 1964 { 1965 if (d->documentLayout->inlineTextObjectManager() == 0) { 1966 return; 1967 } 1968 QString text = block.text(); 1969 int pos = text.indexOf(QChar::ObjectReplacementCharacter, line.textStart()); 1970 1971 while (pos >= 0 && pos <= line.textStart() + line.textLength()) { 1972 QTextCursor c1(block); 1973 c1.setPosition(block.position() + pos); 1974 c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); 1975 1976 KoInlineNote *note = dynamic_cast<KoInlineNote*>(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); 1977 if (note && note->type() == KoInlineNote::Footnote) { 1978 preregisterFootNote(note, bottomOfText); 1979 } 1980 1981 pos = text.indexOf(QChar::ObjectReplacementCharacter, pos + 1); 1982 } 1983 } 1984 1985 qreal KoTextLayoutArea::preregisterFootNote(KoInlineNote *note, qreal bottomOfText) 1986 { 1987 if (d->parent == 0) { 1988 // TODO to support footnotes at end of document this is 1989 // where we need to add some extra condition 1990 if (note->autoNumbering()) { 1991 KoOdfNotesConfiguration *notesConfig = d->documentLayout->styleManager()->notesConfiguration(KoOdfNotesConfiguration::Footnote); 1992 if (notesConfig->numberingScheme() == KoOdfNotesConfiguration::BeginAtDocument) { 1993 note->setAutoNumber(d->footNoteCountInDoc + (d->footNoteAutoCount++)); 1994 } else if (notesConfig->numberingScheme() == KoOdfNotesConfiguration::BeginAtPage) { 1995 note->setAutoNumber(d->footNoteAutoCount++); 1996 } 1997 } 1998 1999 if (maximumAllowedBottom() - bottomOfText > 0) { 2000 QTextFrame *subFrame = note->textFrame(); 2001 d->footNoteCursorToNext = new FrameIterator(subFrame); 2002 KoTextLayoutNoteArea *footNoteArea = new KoTextLayoutNoteArea(note, this, d->documentLayout); 2003 2004 d->preregisteredFootNoteFrames.append(subFrame); 2005 footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom() - bottomOfText); 2006 bool contNotNeeded = footNoteArea->layout(d->footNoteCursorToNext); 2007 if (contNotNeeded) { 2008 delete d->footNoteCursorToNext; 2009 d->footNoteCursorToNext = 0; 2010 d->continuedNoteToNext = 0; 2011 } else { 2012 d->continuedNoteToNext = note; 2013 //layout again now it has set up a continuationObstruction 2014 delete d->footNoteCursorToNext; 2015 d->footNoteCursorToNext = new FrameIterator(subFrame); 2016 footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom() - bottomOfText); 2017 footNoteArea->layout(d->footNoteCursorToNext); 2018 documentLayout()->setContinuationObstruction(0); // remove it again 2019 } 2020 d->preregisteredFootNotesHeight += footNoteArea->bottom() - footNoteArea->top(); 2021 d->preregisteredFootNoteAreas.append(footNoteArea); 2022 return footNoteArea->bottom() - footNoteArea->top(); 2023 } 2024 return 0.0; 2025 } 2026 qreal h = d->parent->preregisterFootNote(note, bottomOfText); 2027 d->preregisteredFootNotesHeight += h; 2028 return h; 2029 } 2030 2031 void KoTextLayoutArea::confirmFootNotes() 2032 { 2033 d->footNotesHeight += d->preregisteredFootNotesHeight; 2034 d->footNoteAreas.append(d->preregisteredFootNoteAreas); 2035 d->footNoteFrames.append(d->preregisteredFootNoteFrames); 2036 d->preregisteredFootNotesHeight = 0; 2037 d->preregisteredFootNoteAreas.clear(); 2038 d->preregisteredFootNoteFrames.clear(); 2039 if (d->parent) { 2040 d->parent->confirmFootNotes(); 2041 } 2042 } 2043 2044 void KoTextLayoutArea::expandBoundingLeft(qreal x) 2045 { 2046 d->boundingRect.setLeft(qMin(x, d->boundingRect.x())); 2047 } 2048 2049 void KoTextLayoutArea::expandBoundingRight(qreal x) 2050 { 2051 d->boundingRect.setRight(qMax(x, d->boundingRect.right())); 2052 } 2053 2054 void KoTextLayoutArea::clearPreregisteredFootNotes() 2055 { 2056 d->preregisteredFootNotesHeight = 0; 2057 d->preregisteredFootNoteAreas.clear(); 2058 d->preregisteredFootNoteFrames.clear(); 2059 if (d->parent) { 2060 d->parent->clearPreregisteredFootNotes(); 2061 } 2062 } 2063 2064 void KoTextLayoutArea::handleBordersAndSpacing(KoTextBlockData &blockData, QTextBlock *block) 2065 { 2066 QTextBlockFormat format = block->blockFormat(); 2067 KoParagraphStyle formatStyle(format, block->charFormat()); 2068 2069 // The AddParaTableSpacingAtStart config-item is used to be able to optionally prevent that 2070 // defined fo:margin-top are applied to the first paragraph. If true then the fo:margin-top 2071 // is applied to all except the first paragraph. If false fo:margin-top is applied to all 2072 // paragraphs. 2073 bool paraTableSpacingAtStart = KoTextDocument(d->documentLayout->document()).paraTableSpacingAtStart(); 2074 bool paddingExpandsBorders = false;//KoTextDocument(d->documentLayout->document()).paddingExpandsBorders(); 2075 2076 qreal topMargin = 0; 2077 if (paraTableSpacingAtStart || block->previous().isValid()) { 2078 topMargin = formatStyle.topMargin(); 2079 } 2080 qreal spacing = qMax(d->bottomSpacing, topMargin); 2081 qreal dx = 0.0; 2082 qreal x = d->x; 2083 qreal width = d->width; 2084 if (d->indent < 0) { 2085 x += d->indent; 2086 width -= d->indent; 2087 } 2088 if (blockData.hasCounterData() && blockData.counterPosition().x() < x) { 2089 width += x - blockData.counterPosition().x(); 2090 x = blockData.counterPosition().x(); 2091 } 2092 2093 KoTextBlockBorderData border(QRectF(x, d->y, width, 1)); 2094 border.setEdge(border.Left, format, KoParagraphStyle::LeftBorderStyle, 2095 KoParagraphStyle::LeftBorderWidth, KoParagraphStyle::LeftBorderColor, 2096 KoParagraphStyle::LeftBorderSpacing, KoParagraphStyle::LeftInnerBorderWidth); 2097 border.setEdge(border.Right, format, KoParagraphStyle::RightBorderStyle, 2098 KoParagraphStyle::RightBorderWidth, KoParagraphStyle::RightBorderColor, 2099 KoParagraphStyle::RightBorderSpacing, KoParagraphStyle::RightInnerBorderWidth); 2100 border.setEdge(border.Top, format, KoParagraphStyle::TopBorderStyle, 2101 KoParagraphStyle::TopBorderWidth, KoParagraphStyle::TopBorderColor, 2102 KoParagraphStyle::TopBorderSpacing, KoParagraphStyle::TopInnerBorderWidth); 2103 border.setEdge(border.Bottom, format, KoParagraphStyle::BottomBorderStyle, 2104 KoParagraphStyle::BottomBorderWidth, KoParagraphStyle::BottomBorderColor, 2105 KoParagraphStyle::BottomBorderSpacing, KoParagraphStyle::BottomInnerBorderWidth); 2106 border.setMergeWithNext(formatStyle.joinBorder()); 2107 2108 if (border.hasBorders()) { 2109 // check if we can merge with the previous parags border. 2110 if (d->prevBorder && d->prevBorder->equals(border)) { 2111 blockData.setBorder(d->prevBorder); 2112 // Merged mean we don't have inserts inbetween the blocks 2113 d->anchoringParagraphTop = d->y; 2114 if (d->bottomSpacing + topMargin) { 2115 d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); 2116 } 2117 if (!d->blockRects.isEmpty()) { 2118 d->blockRects.last().setBottom(d->anchoringParagraphTop); 2119 } 2120 d->anchoringParagraphTop = d->y; 2121 d->y += spacing; 2122 d->blockRects.append(QRectF(x, d->anchoringParagraphTop, width, 1.0)); 2123 } else { 2124 // can't merge; then these are our new borders. 2125 KoTextBlockBorderData *newBorder = new KoTextBlockBorderData(border); 2126 blockData.setBorder(newBorder); 2127 if (d->prevBorder) { 2128 d->y += d->prevBorderPadding; 2129 d->y += d->prevBorder->inset(KoTextBlockBorderData::Bottom); 2130 } 2131 if (!d->blockRects.isEmpty()) { 2132 d->blockRects.last().setBottom(d->y); 2133 } 2134 d->anchoringParagraphTop = d->y; 2135 if (d->bottomSpacing + topMargin) { 2136 d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); 2137 } 2138 d->y += spacing; 2139 if (paddingExpandsBorders) { 2140 d->blockRects.append(QRectF(x - format.doubleProperty(KoParagraphStyle::LeftPadding), d->y, 2141 width + format.doubleProperty(KoParagraphStyle::LeftPadding) + format.doubleProperty(KoParagraphStyle::RightPadding), 1.0)); 2142 } else { 2143 d->blockRects.append(QRectF(x, d->y, width, 1.0)); 2144 } 2145 d->y += newBorder->inset(KoTextBlockBorderData::Top); 2146 d->y += format.doubleProperty(KoParagraphStyle::TopPadding); 2147 } 2148 2149 // finally, horizontal components of the borders 2150 dx = border.inset(KoTextBlockBorderData::Left); 2151 d->x += dx; 2152 d->width -= border.inset(KoTextBlockBorderData::Left); 2153 d->width -= border.inset(KoTextBlockBorderData::Right); 2154 } else { // this parag has no border. 2155 if (d->prevBorder) { 2156 d->y += d->prevBorderPadding; 2157 d->y += d->prevBorder->inset(KoTextBlockBorderData::Bottom); 2158 } 2159 blockData.setBorder(0); // remove an old one, if there was one. 2160 if (!d->blockRects.isEmpty()) { 2161 d->blockRects.last().setBottom(d->y); 2162 } 2163 d->anchoringParagraphTop = d->y; 2164 if (d->bottomSpacing + topMargin) { 2165 d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); 2166 } 2167 d->y += spacing; 2168 d->blockRects.append(QRectF(x, d->y, width, 1.0)); 2169 } 2170 if (!paddingExpandsBorders) { 2171 // add padding inside the border 2172 dx += format.doubleProperty(KoParagraphStyle::LeftPadding); 2173 d->x += format.doubleProperty(KoParagraphStyle::LeftPadding); 2174 d->width -= format.doubleProperty(KoParagraphStyle::LeftPadding); 2175 d->width -= format.doubleProperty(KoParagraphStyle::RightPadding); 2176 } 2177 if (block->layout()->lineCount() == 1 && blockData.hasCounterData()) { 2178 blockData.setCounterPosition(QPointF(blockData.counterPosition().x() + dx, d->y)); 2179 } 2180 d->prevBorder = blockData.border(); 2181 d->prevBorderPadding = format.doubleProperty(KoParagraphStyle::BottomPadding); 2182 d->anchoringParagraphContentTop = d->y; 2183 }