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 &paragraphRect)
0506 {
0507     d->anchoringParagraphRect = paragraphRect;
0508 }
0509 
0510 void KoTextDocumentLayout::setAnchoringParagraphContentRect(const QRectF &paragraphContentRect)
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 }