Warning, file /office/calligra/libs/textlayout/KoTextDocumentLayout.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-2007, 2009-2010 Thomas Zander <zander@kde.org> 0003 * Copyright (C) 2010 Johannes Simon <johannes.simon@gmail.com> 0004 * Copyright (C) 2011-2013 KO GmbH <cbo@kogmbh.com> 0005 * Copyright (C) 2011-2013 C.Boemann <cbo@boemann.dk> 0006 * 0007 * This library is free software; you can redistribute it and/or 0008 * modify it under the terms of the GNU Library General Public 0009 * License as published by the Free Software Foundation; either 0010 * version 2 of the License, or (at your option) any later version. 0011 * 0012 * This library is distributed in the hope that it will be useful, 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 * Library General Public License for more details. 0016 * 0017 * You should have received a copy of the GNU Library General Public License 0018 * along with this library; see the file COPYING.LIB. If not, write to 0019 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0020 * Boston, MA 02110-1301, USA. 0021 */ 0022 0023 #include "KoTextDocumentLayout.h" 0024 0025 #include "styles/KoStyleManager.h" 0026 #include "KoTextBlockData.h" 0027 #include "KoInlineTextObjectManager.h" 0028 #include "KoTextLayoutRootArea.h" 0029 #include "KoTextLayoutRootAreaProvider.h" 0030 #include "KoTextLayoutObstruction.h" 0031 #include "FrameIterator.h" 0032 #include "InlineAnchorStrategy.h" 0033 #include "FloatingAnchorStrategy.h" 0034 #include "AnchorStrategy.h" 0035 #include "IndexGeneratorManager.h" 0036 0037 #include <KoShapeAnchor.h> 0038 #include <KoAnchorInlineObject.h> 0039 #include <KoAnchorTextRange.h> 0040 #include <KoTextRangeManager.h> 0041 #include <KoTextPage.h> 0042 #include <KoPostscriptPaintDevice.h> 0043 #include <KoShape.h> 0044 #include <KoShapeContainer.h> 0045 #include <KoAnnotationLayoutManager.h> 0046 #include <KoAnnotation.h> 0047 #include <KoTextDocument.h> 0048 #include <KoUnit.h> 0049 #include <KoParagraphStyle.h> 0050 #include <KoTableStyle.h> 0051 0052 #include <TextLayoutDebug.h> 0053 #include <QTextBlock> 0054 #include <QTextTable> 0055 #include <QTimer> 0056 #include <QList> 0057 0058 extern int qt_defaultDpiY(); 0059 0060 0061 KoInlineObjectExtent::KoInlineObjectExtent(qreal ascent, qreal descent) 0062 : m_ascent(ascent), 0063 m_descent(descent) 0064 { 0065 } 0066 0067 class Q_DECL_HIDDEN KoTextDocumentLayout::Private 0068 { 0069 public: 0070 Private(KoTextDocumentLayout *) 0071 : styleManager(0) 0072 , changeTracker(0) 0073 , inlineTextObjectManager(0) 0074 , textRangeManager(0) 0075 , provider(0) 0076 , layoutPosition(0) 0077 , anchoringRootArea(0) 0078 , anchoringIndex(0) 0079 , anAnchorIsPlaced(false) 0080 , anchoringSoftBreak(INT_MAX) 0081 , allowPositionInlineObject(true) 0082 , continuationObstruction(0) 0083 , referencedLayout(0) 0084 , annotationLayoutManager(0) 0085 , defaultTabSizing(0) 0086 , y(0) 0087 , isLayouting(false) 0088 , layoutScheduled(false) 0089 , continuousLayout(true) 0090 , layoutBlocked(false) 0091 , changesBlocked(false) 0092 , restartLayout(false) 0093 , wordprocessingMode(false) 0094 , showInlineObjectVisualization(false) 0095 { 0096 } 0097 KoStyleManager *styleManager; 0098 0099 KoChangeTracker *changeTracker; 0100 0101 KoInlineTextObjectManager *inlineTextObjectManager; 0102 KoTextRangeManager *textRangeManager; 0103 KoTextLayoutRootAreaProvider *provider; 0104 KoPostscriptPaintDevice *paintDevice; 0105 QList<KoTextLayoutRootArea *> rootAreaList; 0106 FrameIterator *layoutPosition; 0107 0108 QHash<int, KoInlineObjectExtent> inlineObjectExtents; // maps text-position to whole-line-height of an inline object 0109 int inlineObjectOffset; 0110 QList<KoShapeAnchor *> textAnchors; // list of all inserted inline objects 0111 QList<KoShapeAnchor *> foundAnchors; // anchors found in an iteration run 0112 KoTextLayoutRootArea *anchoringRootArea; 0113 int anchoringIndex; // index of last not positioned inline object inside textAnchors 0114 bool anAnchorIsPlaced; 0115 int anchoringSoftBreak; 0116 QRectF anchoringParagraphRect; 0117 QRectF anchoringParagraphContentRect; 0118 QRectF anchoringLayoutEnvironmentRect; 0119 bool allowPositionInlineObject; 0120 0121 QHash<KoShape*,KoTextLayoutObstruction*> anchoredObstructions; // all obstructions created because KoShapeAnchor from m_textAnchors is in text 0122 QList<KoTextLayoutObstruction*> freeObstructions; // obstructions affecting the current rootArea, and not anchored to text 0123 KoTextLayoutObstruction *continuationObstruction; 0124 0125 KoTextDocumentLayout *referencedLayout; 0126 0127 KoAnnotationLayoutManager *annotationLayoutManager; 0128 0129 QHash<KoInlineObject *, KoTextLayoutRootArea *> rootAreaForInlineObject; 0130 0131 qreal defaultTabSizing; 0132 qreal y; 0133 bool isLayouting; 0134 bool layoutScheduled; 0135 bool continuousLayout; 0136 bool layoutBlocked; 0137 bool changesBlocked; 0138 bool restartLayout; 0139 bool wordprocessingMode; 0140 bool showInlineObjectVisualization; 0141 }; 0142 0143 0144 // ------------------- KoTextDocumentLayout -------------------- 0145 KoTextDocumentLayout::KoTextDocumentLayout(QTextDocument *doc, KoTextLayoutRootAreaProvider *provider) 0146 : QAbstractTextDocumentLayout(doc), 0147 d(new Private(this)) 0148 { 0149 d->paintDevice = new KoPostscriptPaintDevice(); 0150 d->provider = provider; 0151 setPaintDevice(d->paintDevice); 0152 0153 d->styleManager = KoTextDocument(document()).styleManager(); 0154 d->changeTracker = KoTextDocument(document()).changeTracker(); 0155 d->inlineTextObjectManager = KoTextDocument(document()).inlineTextObjectManager(); 0156 d->textRangeManager = KoTextDocument(document()).textRangeManager(); 0157 0158 setTabSpacing(MM_TO_POINT(23)); // use same default as open office 0159 0160 d->layoutPosition = new FrameIterator(doc->rootFrame()); 0161 } 0162 0163 KoTextDocumentLayout::~KoTextDocumentLayout() 0164 { 0165 delete d->paintDevice; 0166 delete d->layoutPosition; 0167 qDeleteAll(d->freeObstructions); 0168 qDeleteAll(d->anchoredObstructions); 0169 qDeleteAll(d->textAnchors); 0170 delete d; 0171 } 0172 0173 KoTextLayoutRootAreaProvider *KoTextDocumentLayout::provider() const 0174 { 0175 return d->provider; 0176 } 0177 0178 void KoTextDocumentLayout::setWordprocessingMode() 0179 { 0180 d->wordprocessingMode = true; 0181 } 0182 0183 bool KoTextDocumentLayout::wordprocessingMode() const 0184 { 0185 return d->wordprocessingMode; 0186 } 0187 0188 0189 bool KoTextDocumentLayout::relativeTabs(const QTextBlock &block) const 0190 { 0191 return KoTextDocument(document()).relativeTabs() 0192 && KoTextDocument(block.document()).relativeTabs(); 0193 } 0194 0195 KoInlineTextObjectManager *KoTextDocumentLayout::inlineTextObjectManager() const 0196 { 0197 return d->inlineTextObjectManager; 0198 } 0199 0200 void KoTextDocumentLayout::setInlineTextObjectManager(KoInlineTextObjectManager *manager) 0201 { 0202 d->inlineTextObjectManager = manager; 0203 } 0204 0205 KoTextRangeManager *KoTextDocumentLayout::textRangeManager() const 0206 { 0207 return d->textRangeManager; 0208 } 0209 0210 void KoTextDocumentLayout::setTextRangeManager(KoTextRangeManager *manager) 0211 { 0212 d->textRangeManager = manager; 0213 } 0214 0215 KoChangeTracker *KoTextDocumentLayout::changeTracker() const 0216 { 0217 return d->changeTracker; 0218 } 0219 0220 void KoTextDocumentLayout::setChangeTracker(KoChangeTracker *tracker) 0221 { 0222 d->changeTracker = tracker; 0223 } 0224 0225 KoStyleManager *KoTextDocumentLayout::styleManager() const 0226 { 0227 return d->styleManager; 0228 } 0229 0230 void KoTextDocumentLayout::setStyleManager(KoStyleManager *manager) 0231 { 0232 d->styleManager = manager; 0233 } 0234 0235 QRectF KoTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const 0236 { 0237 QTextLayout *layout = block.layout(); 0238 return layout->boundingRect(); 0239 } 0240 0241 QSizeF KoTextDocumentLayout::documentSize() const 0242 { 0243 return QSizeF(); 0244 } 0245 0246 QRectF KoTextDocumentLayout::selectionBoundingBox(QTextCursor &cursor) const 0247 { 0248 QRectF retval; 0249 foreach(const KoTextLayoutRootArea *rootArea, d->rootAreaList) { 0250 if (!rootArea->isDirty()) { 0251 QRectF areaBB = rootArea->selectionBoundingBox(cursor); 0252 if (areaBB.isValid()) { 0253 retval |= areaBB; 0254 } 0255 } 0256 } 0257 return retval; 0258 } 0259 0260 0261 void KoTextDocumentLayout::draw(QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context) 0262 { 0263 // WARNING Text shapes ask their root area directly to paint. 0264 // It saves a lot of extra traversal, that is quite costly for big 0265 // documents 0266 Q_UNUSED(painter); 0267 Q_UNUSED(context); 0268 } 0269 0270 0271 int KoTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const 0272 { 0273 Q_UNUSED(point); 0274 Q_UNUSED(accuracy); 0275 Q_ASSERT(false); //we should no longer call this method. 0276 // There is no need and is just slower than needed 0277 // call rootArea->hitTest() directly 0278 // root area is available through KoTextShapeData 0279 return -1; 0280 } 0281 0282 int KoTextDocumentLayout::pageCount() const 0283 { 0284 return 1; 0285 } 0286 0287 void KoTextDocumentLayout::setTabSpacing(qreal spacing) 0288 { 0289 d->defaultTabSizing = spacing; 0290 } 0291 0292 qreal KoTextDocumentLayout::defaultTabSpacing() const 0293 { 0294 return d->defaultTabSizing; 0295 } 0296 0297 // this method is called on every char inserted or deleted, on format changes, setting/moving of variables or objects. 0298 void KoTextDocumentLayout::documentChanged(int position, int charsRemoved, int charsAdded) 0299 { 0300 if (d->changesBlocked) { 0301 return; 0302 } 0303 0304 int from = position; 0305 const int to = from + charsAdded; 0306 while (from < to) { // find blocks that have been added 0307 QTextBlock block = document()->findBlock(from); 0308 if (! block.isValid()) 0309 break; 0310 if (from == block.position() && block.textList()) { 0311 KoTextBlockData data(block); 0312 data.setCounterWidth(-1); 0313 } 0314 0315 from = block.position() + block.length(); 0316 } 0317 0318 // Mark the to the position corresponding root-areas as dirty. If there is no root-area for the position then we 0319 // don't need to mark anything dirty but still need to go on to force a scheduled relayout. 0320 if (!d->rootAreaList.isEmpty()) { 0321 KoTextLayoutRootArea *fromArea; 0322 if (position) { 0323 fromArea = rootAreaForPosition(position-1); 0324 } else { 0325 fromArea = d->rootAreaList.at(0); 0326 } 0327 int startIndex = fromArea ? qMax(0, d->rootAreaList.indexOf(fromArea)) : 0; 0328 int endIndex = startIndex; 0329 if (charsRemoved != 0 || charsAdded != 0) { 0330 // If any characters got removed or added make sure to also catch other root-areas that may be 0331 // affected by this change. Note that adding, removing or formatting text will always charsRemoved>0 0332 // and charsAdded>0 cause they are changing a range of characters. One case where both is zero is if 0333 // the content of a variable changed (see KoVariable::setValue which calls publicDocumentChanged). In 0334 // those cases we only need to relayout the root-area dirty where the variable is on. 0335 KoTextLayoutRootArea *toArea = fromArea ? rootAreaForPosition(position + qMax(charsRemoved, charsAdded) + 1) : 0; 0336 if (toArea) { 0337 if (toArea != fromArea) { 0338 endIndex = qMax(startIndex, d->rootAreaList.indexOf(toArea)); 0339 } else { 0340 endIndex = startIndex; 0341 } 0342 } else { 0343 endIndex = d->rootAreaList.count() - 1; 0344 } 0345 // The previous and following root-area of that range are selected too cause they can also be affect by 0346 // changes done to the range of root-areas. 0347 if (startIndex >= 1) 0348 --startIndex; 0349 if (endIndex + 1 < d->rootAreaList.count()) 0350 ++endIndex; 0351 } 0352 // Mark all selected root-areas as dirty so they are relayouted. 0353 for(int i = startIndex; i <= endIndex; ++i) { 0354 if (d->rootAreaList.size() > i && d->rootAreaList[i]) 0355 d->rootAreaList[i]->setDirty(); 0356 } 0357 } 0358 0359 // Once done we emit the layoutIsDirty signal. The consumer (e.g. the TextShape) will then layout dirty 0360 // root-areas and if needed following ones which got dirty cause content moved to them. Also this will 0361 // created new root-areas using KoTextLayoutRootAreaProvider::provide if needed. 0362 emitLayoutIsDirty(); 0363 } 0364 0365 KoTextLayoutRootArea *KoTextDocumentLayout::rootAreaForPosition(int position) const 0366 { 0367 QTextBlock block = document()->findBlock(position); 0368 if (!block.isValid()) 0369 return 0; 0370 QTextLine line = block.layout()->lineForTextPosition(position - block.position()); 0371 if (!line.isValid()) 0372 return 0; 0373 0374 foreach (KoTextLayoutRootArea *rootArea, d->rootAreaList) { 0375 QRectF rect = rootArea->boundingRect(); // should already be normalized() 0376 if (rect.width() <= 0.0 && rect.height() <= 0.0) // ignore the rootArea if it has a size of QSizeF(0,0) 0377 continue; 0378 QPointF pos = line.position(); 0379 qreal x = pos.x(); 0380 qreal y = pos.y(); 0381 0382 //0.125 needed since Qt Scribe works with fixed point 0383 if (x + 0.125 >= rect.x() && x<= rect.right() && y + line.height() + 0.125 >= rect.y() && y <= rect.bottom()) { 0384 return rootArea; 0385 } 0386 } 0387 return 0; 0388 } 0389 0390 KoTextLayoutRootArea *KoTextDocumentLayout::rootAreaForPoint(const QPointF &point) const 0391 { 0392 foreach(KoTextLayoutRootArea *rootArea, d->rootAreaList) { 0393 if (!rootArea->isDirty()) { 0394 if (rootArea->boundingRect().contains(point)) { 0395 return rootArea; 0396 } 0397 } 0398 } 0399 return 0; 0400 } 0401 0402 void KoTextDocumentLayout::showInlineObjectVisualization(bool show) 0403 { 0404 d->showInlineObjectVisualization = show; 0405 } 0406 0407 void KoTextDocumentLayout::drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int position, const QTextFormat &format) 0408 { 0409 Q_ASSERT(format.isCharFormat()); 0410 if (d->inlineTextObjectManager == 0) 0411 return; 0412 QTextCharFormat cf = format.toCharFormat(); 0413 if (d->showInlineObjectVisualization) { 0414 QColor color = cf.foreground().color(); 0415 // initial idea was to use Qt::gray (#A0A0A4) 0416 // for non-black text on non-white background it was derived to use 0417 // the text color with a transparency of 0x5F, so white-gray (0xFF-0xA0) 0418 color.setAlpha(0x5F); 0419 cf.setBackground(QBrush(color)); 0420 } 0421 KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf); 0422 if (obj) 0423 obj->paint(*painter, paintDevice(), document(), rect, object, position, cf); 0424 } 0425 0426 QList<KoShapeAnchor *> KoTextDocumentLayout::textAnchors() const 0427 { 0428 return d->textAnchors; 0429 } 0430 0431 void KoTextDocumentLayout::registerAnchoredObstruction(KoTextLayoutObstruction *obstruction) 0432 { 0433 d->anchoredObstructions.insert(obstruction->shape(), obstruction); 0434 } 0435 0436 qreal KoTextDocumentLayout::maxYOfAnchoredObstructions(int firstCursorPosition, int lastCursorPosition) const 0437 { 0438 qreal y = 0.0; 0439 int index = 0; 0440 0441 while (index < d->anchoringIndex) { 0442 Q_ASSERT(index < d->textAnchors.count()); 0443 KoShapeAnchor *anchor = d->textAnchors[index]; 0444 if (anchor->flowWithText()) { 0445 if (anchor->textLocation()->position() >= firstCursorPosition 0446 && anchor->textLocation()->position() <= lastCursorPosition) { 0447 y = qMax(y, anchor->shape()->boundingRect().bottom() - anchor->shape()->parent()->boundingRect().y()); 0448 } 0449 } 0450 ++index; 0451 } 0452 return y; 0453 } 0454 0455 int KoTextDocumentLayout::anchoringSoftBreak() const 0456 { 0457 return d->anchoringSoftBreak; 0458 } 0459 void KoTextDocumentLayout::positionAnchoredObstructions() 0460 { 0461 if (!d->anchoringRootArea) 0462 return; 0463 KoTextPage *page = d->anchoringRootArea->page(); 0464 if (!page) 0465 return; 0466 if (d->anAnchorIsPlaced) 0467 return; 0468 0469 // The specs define 3 different anchor modes using the 0470 // draw:wrap-influence-on-position. We only implement the 0471 // once-successive and decided against supporting the other 0472 // two modes cause; 0473 // 1. The first mode, once-concurrently, is only for backward-compatibility 0474 // with pre OpenOffice.org 1.1. No other application supports that. It 0475 // should never have been added to the specs. 0476 // 2. The iterative mode is undocumented and it's absolute unclear how to 0477 // implement it in a way that we would earn 100% the same results OO.org 0478 // produces. In fact by looking at the OO.org source-code there seem to 0479 // be lot of extra-conditions, assumptions and OO.org related things going 0480 // on to handle that mode. We tried to support that mode once and it did 0481 // hit us bad, our source-code become way more worse, layouting slower and 0482 // the result was still different from OO.org. So, we decided it's not 0483 // worth it. 0484 // 3. The explanation provided at http://lists.oasis-open.org/archives/office/200409/msg00018.html 0485 // why the specs support those 3 anchor modes is, well, poor. It just doesn't 0486 // make sense. The specs should be fixed. 0487 // 4. The only support mode, the once-successive, is the one (only) support by 0488 // MSOffice. It's clear, logical, easy and needs to be supported by all 0489 // major office-suites that like to be compatible with MSOffice and OO.org. 0490 if (d->anchoringIndex < d->textAnchors.count()) { 0491 KoShapeAnchor *textAnchor = d->textAnchors[d->anchoringIndex]; 0492 AnchorStrategy *strategy = static_cast<AnchorStrategy *>(textAnchor->placementStrategy()); 0493 0494 strategy->setPageRect(page->rect()); 0495 strategy->setPageContentRect(page->contentRect()); 0496 strategy->setPageNumber(page->pageNumber()); 0497 0498 if (strategy->moveSubject()) { 0499 ++d->anchoringIndex; 0500 d->anAnchorIsPlaced = true; 0501 } 0502 } 0503 } 0504 0505 void KoTextDocumentLayout::setAnchoringParagraphRect(const QRectF ¶graphRect) 0506 { 0507 d->anchoringParagraphRect = paragraphRect; 0508 } 0509 0510 void KoTextDocumentLayout::setAnchoringParagraphContentRect(const QRectF ¶graphContentRect) 0511 { 0512 d->anchoringParagraphContentRect = paragraphContentRect; 0513 } 0514 0515 void KoTextDocumentLayout::setAnchoringLayoutEnvironmentRect(const QRectF &layoutEnvironmentRect) 0516 { 0517 d->anchoringLayoutEnvironmentRect = layoutEnvironmentRect; 0518 } 0519 0520 void KoTextDocumentLayout::allowPositionInlineObject(bool allow) 0521 { 0522 d->allowPositionInlineObject = allow; 0523 } 0524 0525 // This method is called by qt every time QTextLine.setWidth()/setNumColumns() is called 0526 void KoTextDocumentLayout::positionInlineObject(QTextInlineObject item, int position, const QTextFormat &format) 0527 { 0528 // Note: "item" used to be what was positioned. We don't actually use qtextinlineobjects anymore 0529 // for our inline objects, but get the id from the format. 0530 Q_UNUSED(item); 0531 //We are called before layout so that we can position objects 0532 Q_ASSERT(format.isCharFormat()); 0533 if (d->inlineTextObjectManager == 0) 0534 return; 0535 if (!d->allowPositionInlineObject) 0536 return; 0537 QTextCharFormat cf = format.toCharFormat(); 0538 KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf); 0539 // We need some special treatment for anchors as they need to position their object during 0540 // layout and not this early 0541 KoAnchorInlineObject *anchorObject = dynamic_cast<KoAnchorInlineObject *>(obj); 0542 if (anchorObject && d->anchoringRootArea->associatedShape()) { 0543 // The type can only be KoShapeAnchor::AnchorAsCharacter since it's inline 0544 KoShapeAnchor *anchor = anchorObject->anchor(); 0545 d->foundAnchors.append(anchor); 0546 0547 // if there is no anchor strategy set then create one 0548 if (!anchor->placementStrategy()) { 0549 anchor->setPlacementStrategy(new InlineAnchorStrategy(anchorObject, d->anchoringRootArea)); 0550 d->textAnchors.append(anchor); 0551 anchorObject->updatePosition(document(), position, cf); // by extension calls updateContainerModel 0552 } 0553 static_cast<AnchorStrategy *>(anchor->placementStrategy())->setParagraphRect(d->anchoringParagraphRect); 0554 static_cast<AnchorStrategy *>(anchor->placementStrategy())->setParagraphContentRect(d->anchoringParagraphContentRect); 0555 static_cast<AnchorStrategy *>(anchor->placementStrategy())->setLayoutEnvironmentRect(d->anchoringLayoutEnvironmentRect); 0556 } 0557 else if (obj) { 0558 obj->updatePosition(document(), position, cf); 0559 } 0560 } 0561 0562 // This method is called by KoTextLauoutArea every time it encounters a KoAnchorTextRange 0563 void KoTextDocumentLayout::positionAnchorTextRanges(int pos, int length, const QTextDocument *effectiveDocument) 0564 { 0565 if (!d->allowPositionInlineObject) 0566 return; 0567 0568 if (!textRangeManager()) { 0569 return; 0570 } 0571 QHash<int, KoTextRange *> ranges = textRangeManager()->textRangesChangingWithin(effectiveDocument, pos, pos+length, pos, pos+length); 0572 0573 foreach(KoTextRange *range, ranges) { 0574 KoAnchorTextRange *anchorRange = dynamic_cast<KoAnchorTextRange *>(range); 0575 if (anchorRange) { 0576 // We need some special treatment for anchors as they need to position their object during 0577 // layout and not this early 0578 KoShapeAnchor *anchor = anchorRange->anchor(); 0579 d->foundAnchors.append(anchor); 0580 0581 // At the beginAnchorCollecting the strategy is cleared, so this if will be entered 0582 // every time we layout a page (though not every time for the inner repeats due to anchors) 0583 if (!anchor->placementStrategy()) { 0584 int index = d->textAnchors.count(); 0585 anchor->setPlacementStrategy(new FloatingAnchorStrategy(anchorRange, d->anchoringRootArea)); 0586 0587 // The purpose of following code-block is to be sure that our paragraph-anchors are 0588 // properly sorted by their z-index so the FloatingAnchorStrategy::checkStacking 0589 // logic stack in the proper order. Bug 274512 has a testdoc for this attached. 0590 if (index > 0 && 0591 anchor->anchorType() == KoShapeAnchor::AnchorParagraph && 0592 (anchor->horizontalRel() == KoShapeAnchor::HParagraph || anchor->horizontalRel() == KoShapeAnchor::HParagraphContent) && 0593 (anchor->horizontalPos() == KoShapeAnchor::HLeft || anchor->horizontalPos() == KoShapeAnchor::HRight)) { 0594 QTextBlock anchorBlock = document()->findBlock(anchorRange->position()); 0595 for(int i = index - 1; i >= 0; --i) { 0596 KoShapeAnchor *a = d->textAnchors[i]; 0597 if (a->anchorType() != anchor->anchorType()) 0598 break; 0599 if (a->horizontalPos() != anchor->horizontalPos()) 0600 break; 0601 if (document()->findBlock(a->textLocation()->position()) != anchorBlock) 0602 break; 0603 if (a->shape()->zIndex() < anchor->shape()->zIndex()) 0604 break; 0605 --index; 0606 } 0607 } 0608 d->textAnchors.insert(index, anchor); 0609 anchorRange->updateContainerModel(); 0610 } 0611 static_cast<AnchorStrategy *>(anchor->placementStrategy())->setParagraphRect(d->anchoringParagraphRect); 0612 static_cast<AnchorStrategy *>(anchor->placementStrategy())->setParagraphContentRect(d->anchoringParagraphContentRect); 0613 static_cast<AnchorStrategy *>(anchor->placementStrategy())->setLayoutEnvironmentRect(d->anchoringLayoutEnvironmentRect); 0614 } 0615 KoAnnotation *annotation = dynamic_cast<KoAnnotation *>(range); 0616 if (annotation) { 0617 int position = range->rangeStart(); 0618 QTextBlock block = range->document()->findBlock(position); 0619 QTextLine line = block.layout()->lineForTextPosition(position - block.position()); 0620 QPointF refPos(line.cursorToX(position - block.position()), line.y()); 0621 0622 KoShape *refShape = d->anchoringRootArea->associatedShape(); 0623 //KoTextShapeData *refTextShapeData; 0624 //refPos += QPointF(refTextShapeData->leftPadding(), -refTextShapeData->documentOffset() + refTextShapeData->topPadding()); 0625 0626 refPos += QPointF(0, -d->anchoringRootArea->top()); 0627 refPos = refShape->absoluteTransformation(0).map(refPos); 0628 0629 //FIXME we need a more precise position than anchorParagraph Rect 0630 emit foundAnnotation(annotation->annotationShape(), refPos); 0631 } 0632 } 0633 } 0634 0635 void KoTextDocumentLayout::beginAnchorCollecting(KoTextLayoutRootArea *rootArea) 0636 { 0637 for(int i = 0; i<d->textAnchors.size(); i++ ) { 0638 d->textAnchors[i]->setPlacementStrategy(0); 0639 } 0640 0641 qDeleteAll(d->anchoredObstructions); 0642 d->anchoredObstructions.clear(); 0643 d->textAnchors.clear(); 0644 0645 d->anchoringIndex = 0; 0646 d->anAnchorIsPlaced = false; 0647 d->anchoringRootArea = rootArea; 0648 d->allowPositionInlineObject = true; 0649 d->anchoringSoftBreak = INT_MAX; 0650 } 0651 0652 void KoTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int position, const QTextFormat &format) 0653 { 0654 // Note: This method is called by qt during layout AND during paint 0655 Q_ASSERT(format.isCharFormat()); 0656 if (d->inlineTextObjectManager == 0) 0657 return; 0658 QTextCharFormat cf = format.toCharFormat(); 0659 KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf); 0660 0661 if (!obj) { 0662 return; 0663 } 0664 0665 if (d->isLayouting) { 0666 d->rootAreaForInlineObject[obj] = d->anchoringRootArea; 0667 } 0668 KoTextLayoutRootArea *rootArea = d->rootAreaForInlineObject.value(obj); 0669 0670 if (rootArea == 0 || rootArea->associatedShape() == 0) 0671 return; 0672 0673 QTextDocument *doc = document(); 0674 QVariant v; 0675 v.setValue(rootArea->page()); 0676 doc->addResource(KoTextDocument::LayoutTextPage, KoTextDocument::LayoutTextPageUrl, v); 0677 obj->resize(doc, item, position, cf, paintDevice()); 0678 registerInlineObject(item); 0679 } 0680 0681 void KoTextDocumentLayout::emitLayoutIsDirty() 0682 { 0683 emit layoutIsDirty(); 0684 } 0685 0686 void KoTextDocumentLayout::layout() 0687 { 0688 if (d->layoutBlocked) { 0689 return; 0690 } 0691 0692 if (IndexGeneratorManager::instance(document())->generate()) { 0693 return; 0694 } 0695 0696 Q_ASSERT(!d->isLayouting); 0697 d->isLayouting = true; 0698 0699 bool finished; 0700 do { 0701 // Try to layout as long as d->restartLayout==true. This can happen for example if 0702 // a schedule layout call interrupts the layouting and asks for a new layout run. 0703 finished = doLayout(); 0704 } while (d->restartLayout); 0705 0706 Q_ASSERT(d->isLayouting); 0707 d->isLayouting = false; 0708 0709 if (finished) { 0710 // We are only finished with layouting if continuousLayout()==true. 0711 emit finishedLayout(); 0712 } 0713 } 0714 0715 RootAreaConstraint constraintsForPosition(QTextFrame::iterator it, bool previousIsValid) 0716 { 0717 RootAreaConstraint constraints; 0718 constraints.visiblePageNumber = -1; 0719 constraints.newPageForced = false; 0720 QTextBlock block = it.currentBlock(); 0721 QTextTable *table = qobject_cast<QTextTable*>(it.currentFrame()); 0722 if (block.isValid()) { 0723 constraints.masterPageName = block.blockFormat().stringProperty(KoParagraphStyle::MasterPageName); 0724 if (block.blockFormat().hasProperty(KoParagraphStyle::PageNumber)) { 0725 constraints.visiblePageNumber = block.blockFormat().intProperty(KoParagraphStyle::PageNumber); 0726 } 0727 constraints.newPageForced = block.blockFormat().intProperty(KoParagraphStyle::BreakBefore) == KoText::PageBreak; 0728 } 0729 if (table) { 0730 constraints.masterPageName = table->frameFormat().stringProperty(KoTableStyle::MasterPageName); 0731 if (table->frameFormat().hasProperty(KoTableStyle::PageNumber)) { 0732 constraints.visiblePageNumber = table->frameFormat().intProperty(KoTableStyle::PageNumber); 0733 } 0734 constraints.newPageForced = table->frameFormat().intProperty(KoTableStyle::BreakBefore) == KoText::PageBreak; 0735 } 0736 0737 if (!constraints.masterPageName.isEmpty()) { 0738 constraints.newPageForced = true; 0739 } 0740 if (previousIsValid && !constraints.newPageForced) { 0741 it--; 0742 block = it.currentBlock(); 0743 table = qobject_cast<QTextTable*>(it.currentFrame()); 0744 if (block.isValid()) { 0745 constraints.newPageForced = block.blockFormat().intProperty(KoParagraphStyle::BreakAfter) == KoText::PageBreak; 0746 } 0747 if (table) { 0748 constraints.newPageForced = table->frameFormat().intProperty(KoTableStyle::BreakAfter) == KoText::PageBreak; 0749 } 0750 } 0751 0752 return constraints; 0753 } 0754 0755 bool KoTextDocumentLayout::doLayout() 0756 { 0757 delete d->layoutPosition; 0758 d->layoutPosition = new FrameIterator(document()->rootFrame()); 0759 d->y = 0; 0760 d->layoutScheduled = false; 0761 d->restartLayout = false; 0762 FrameIterator *transferedFootNoteCursor = 0; 0763 KoInlineNote *transferedContinuedNote = 0; 0764 int footNoteAutoCount = 0; 0765 KoTextLayoutRootArea *rootArea = 0; 0766 0767 d->rootAreaList.clear(); 0768 0769 int currentAreaNumber = 0; 0770 do { 0771 if (d->restartLayout) { 0772 return false; // Abort layouting to restart from the beginning. 0773 } 0774 0775 // Build our request for our rootArea provider 0776 RootAreaConstraint constraints = constraintsForPosition(d->layoutPosition->it, currentAreaNumber > 0); 0777 0778 // Request a new root-area. If NULL is returned then layouting is finished. 0779 bool newRootArea = false; 0780 rootArea = d->provider->provide(this, constraints, currentAreaNumber, &newRootArea); 0781 if (!rootArea) { 0782 // Out of space ? Nothing more to do 0783 break; 0784 } 0785 0786 d->rootAreaList.append(rootArea); 0787 bool shouldLayout = false; 0788 0789 if (rootArea->top() != d->y) { 0790 shouldLayout = true; 0791 } 0792 else if (rootArea->isDirty()) { 0793 shouldLayout = true; 0794 } 0795 else if (!rootArea->isStartingAt(d->layoutPosition)) { 0796 shouldLayout = true; 0797 } 0798 else if (newRootArea) { 0799 shouldLayout = true; 0800 } 0801 0802 if (shouldLayout) { 0803 QRectF rect = d->provider->suggestRect(rootArea); 0804 d->freeObstructions = d->provider->relevantObstructions(rootArea); 0805 0806 rootArea->setReferenceRect(rect.left(), rect.right(), d->y + rect.top(), d->y + rect.bottom()); 0807 0808 beginAnchorCollecting(rootArea); 0809 0810 // Layout all that can fit into that root area 0811 bool finished; 0812 FrameIterator *tmpPosition = 0; 0813 do { 0814 rootArea->setFootNoteCountInDoc(footNoteAutoCount); 0815 rootArea->setFootNoteFromPrevious(transferedFootNoteCursor, transferedContinuedNote); 0816 d->foundAnchors.clear(); 0817 delete tmpPosition; 0818 tmpPosition = new FrameIterator(d->layoutPosition); 0819 finished = rootArea->layoutRoot(tmpPosition); 0820 if (d->anAnchorIsPlaced) { 0821 d->anAnchorIsPlaced = false; 0822 } else { 0823 ++d->anchoringIndex; 0824 } 0825 } while (d->anchoringIndex < d->textAnchors.count()); 0826 0827 foreach (KoShapeAnchor *anchor, d->textAnchors) { 0828 if (!d->foundAnchors.contains(anchor)) { 0829 d->anchoredObstructions.remove(anchor->shape()); 0830 d->anchoringSoftBreak = qMin(d->anchoringSoftBreak, anchor->textLocation()->position()); 0831 } 0832 } 0833 0834 if (d->textAnchors.count() > 0) { 0835 delete tmpPosition; 0836 tmpPosition = new FrameIterator(d->layoutPosition); 0837 finished = rootArea->layoutRoot(tmpPosition); 0838 } 0839 delete d->layoutPosition; 0840 d->layoutPosition = tmpPosition; 0841 0842 d->provider->doPostLayout(rootArea, newRootArea); 0843 updateProgress(d->layoutPosition->it); 0844 0845 if (finished && !rootArea->footNoteCursorToNext()) { 0846 d->provider->releaseAllAfter(rootArea); 0847 // We must also delete them from our own list too 0848 int newsize = d->rootAreaList.indexOf(rootArea) + 1; 0849 while (d->rootAreaList.size() > newsize) { 0850 d->rootAreaList.removeLast(); 0851 } 0852 return true; // Finished layouting 0853 } 0854 0855 if (d->layoutPosition->it == document()->rootFrame()->end()) { 0856 return true; // Finished layouting 0857 } 0858 0859 if (!continuousLayout()) { 0860 return false; // Let's take a break. We are not finished layouting yet. 0861 } 0862 } else { 0863 // Drop following rootAreas 0864 delete d->layoutPosition; 0865 d->layoutPosition = new FrameIterator(rootArea->nextStartOfArea()); 0866 if (d->layoutPosition->it == document()->rootFrame()->end() && !rootArea->footNoteCursorToNext()) { 0867 d->provider->releaseAllAfter(rootArea); 0868 // We must also delete them from our own list too 0869 int newsize = d->rootAreaList.indexOf(rootArea) + 1; 0870 while (d->rootAreaList.size() > newsize) { 0871 d->rootAreaList.removeLast(); 0872 } 0873 return true; // Finished layouting 0874 } 0875 } 0876 transferedFootNoteCursor = rootArea->footNoteCursorToNext(); 0877 transferedContinuedNote = rootArea->continuedNoteToNext(); 0878 footNoteAutoCount += rootArea->footNoteAutoCount(); 0879 0880 d->y = rootArea->bottom() + qreal(50); // (post)Layout method(s) just set this 0881 // 50 just to separate pages 0882 currentAreaNumber++; 0883 } while (transferedFootNoteCursor || d->layoutPosition->it != document()->rootFrame()->end()); 0884 0885 return true; // Finished layouting 0886 } 0887 0888 void KoTextDocumentLayout::scheduleLayout() 0889 { 0890 // Compress multiple scheduleLayout calls into one executeScheduledLayout. 0891 if (d->layoutScheduled) { 0892 return; 0893 } 0894 d->layoutScheduled = true; 0895 QTimer::singleShot(0, this, SLOT(executeScheduledLayout())); 0896 } 0897 0898 void KoTextDocumentLayout::executeScheduledLayout() 0899 { 0900 // Only do the actual layout if it wasn't done meanwhile by someone else. 0901 if (!d->layoutScheduled) { 0902 return; 0903 } 0904 d->layoutScheduled = false; 0905 if (d->isLayouting) { 0906 // Since we are already layouting ask for a restart to be sure to also include 0907 // root-areas that got dirty and are before the currently processed root-area. 0908 d->restartLayout = true; 0909 } else { 0910 layout(); 0911 } 0912 } 0913 0914 bool KoTextDocumentLayout::continuousLayout() const 0915 { 0916 return d->continuousLayout; 0917 } 0918 0919 void KoTextDocumentLayout::setContinuousLayout(bool continuous) 0920 { 0921 d->continuousLayout = continuous; 0922 } 0923 0924 void KoTextDocumentLayout::setBlockLayout(bool block) 0925 { 0926 d->layoutBlocked = block; 0927 } 0928 0929 bool KoTextDocumentLayout::layoutBlocked() const 0930 { 0931 return d->layoutBlocked; 0932 } 0933 0934 void KoTextDocumentLayout::setBlockChanges(bool block) 0935 { 0936 d->changesBlocked = block; 0937 } 0938 0939 bool KoTextDocumentLayout::changesBlocked() const 0940 { 0941 return d->changesBlocked; 0942 } 0943 0944 KoTextDocumentLayout* KoTextDocumentLayout::referencedLayout() const 0945 { 0946 return d->referencedLayout; 0947 } 0948 0949 void KoTextDocumentLayout::setReferencedLayout(KoTextDocumentLayout *layout) 0950 { 0951 d->referencedLayout = layout; 0952 } 0953 0954 QRectF KoTextDocumentLayout::frameBoundingRect(QTextFrame*) const 0955 { 0956 return QRectF(); 0957 } 0958 0959 void KoTextDocumentLayout::clearInlineObjectRegistry(const QTextBlock &block) 0960 { 0961 d->inlineObjectExtents.clear(); 0962 d->inlineObjectOffset = block.position(); 0963 } 0964 0965 void KoTextDocumentLayout::registerInlineObject(const QTextInlineObject &inlineObject) 0966 { 0967 KoInlineObjectExtent pos(inlineObject.ascent(),inlineObject.descent()); 0968 d->inlineObjectExtents.insert(d->inlineObjectOffset + inlineObject.textPosition(), pos); 0969 } 0970 0971 KoInlineObjectExtent KoTextDocumentLayout::inlineObjectExtent(const QTextFragment &fragment) 0972 { 0973 if (d->inlineObjectExtents.contains(fragment.position())) 0974 return d->inlineObjectExtents[fragment.position()]; 0975 return KoInlineObjectExtent(); 0976 } 0977 0978 void KoTextDocumentLayout::setContinuationObstruction(KoTextLayoutObstruction *continuationObstruction) 0979 { 0980 if (d->continuationObstruction) { 0981 delete d->continuationObstruction; 0982 } 0983 d->continuationObstruction = continuationObstruction; 0984 } 0985 0986 QList<KoTextLayoutObstruction *> KoTextDocumentLayout::currentObstructions() 0987 { 0988 if (d->continuationObstruction) { 0989 // () is needed so we append to a local list and not anchoredObstructions 0990 return (d->freeObstructions + d->anchoredObstructions.values()) << d->continuationObstruction; 0991 } else { 0992 return d->freeObstructions + d->anchoredObstructions.values(); 0993 } 0994 } 0995 0996 QList<KoTextLayoutRootArea *> KoTextDocumentLayout::rootAreas() const 0997 { 0998 return d->rootAreaList; 0999 } 1000 1001 void KoTextDocumentLayout::removeRootArea(KoTextLayoutRootArea *rootArea) 1002 { 1003 int indexOf = rootArea ? qMax(0, d->rootAreaList.indexOf(rootArea)) : 0; 1004 for(int i = d->rootAreaList.count() - 1; i >= indexOf; --i) 1005 d->rootAreaList.removeAt(i); 1006 } 1007 1008 QList<KoShape*> KoTextDocumentLayout::shapes() const 1009 { 1010 QList<KoShape*> listOfShapes; 1011 foreach (KoTextLayoutRootArea *rootArea, d->rootAreaList) { 1012 if (rootArea->associatedShape()) 1013 listOfShapes.append(rootArea->associatedShape()); 1014 } 1015 return listOfShapes; 1016 } 1017 1018 void KoTextDocumentLayout::updateProgress(const QTextFrame::iterator &it) 1019 { 1020 QTextBlock block = it.currentBlock(); 1021 if (block.isValid()) { 1022 int percent = block.position() / qreal(document()->rootFrame()->lastPosition()) * 100.0; 1023 emit layoutProgressChanged(percent); 1024 } else if (it.currentFrame()) { 1025 int percent = it.currentFrame()->firstPosition() / qreal(document()->rootFrame()->lastPosition()) * 100.0; 1026 emit layoutProgressChanged(percent); 1027 } 1028 }