Warning, file /office/calligra/libs/textlayout/RunAroundHelper.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, 2010 Thomas Zander <zander@kde.org>
0003  * Copyright (C) 2010-2011 KO Gmbh <cbo@kogmbh.com>
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Library General Public
0007  * License as published by the Free Software Foundation; either
0008  * version 2 of the License, or (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Library General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Library General Public License
0016  * along with this library; see the file COPYING.LIB.  If not, write to
0017  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019  */
0020 #include "RunAroundHelper.h"
0021 
0022 #include "KoTextLayoutObstruction.h"
0023 
0024 #include "KoTextLayoutArea.h"
0025 
0026 #include <QDebug>
0027 
0028 #include <algorithm>
0029 
0030 const qreal RIDICULOUSLY_LARGE_NEGATIVE_INDENT = -5E6;
0031 #define MIN_WIDTH   0.01f
0032 
0033 RunAroundHelper::RunAroundHelper()
0034 {
0035     m_lineRect = QRectF();
0036     m_updateValidObstructions = false;
0037     m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT;
0038     m_stayOnBaseline = false;
0039 }
0040 
0041 void RunAroundHelper::setLine(KoTextLayoutArea *area, const QTextLine &l) {
0042     m_area = area;
0043     line = l;
0044 }
0045 
0046 void RunAroundHelper::setObstructions(const QList<KoTextLayoutObstruction*> &obstructions)
0047 {
0048     m_obstructions = obstructions;
0049 }
0050 
0051 bool RunAroundHelper::stayOnBaseline() const
0052 {
0053     return m_stayOnBaseline;
0054 }
0055 
0056 void RunAroundHelper::updateObstruction(KoTextLayoutObstruction *obstruction)
0057 {
0058     QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect);
0059     if (obstructionLineRect.isValid()) {
0060         m_updateValidObstructions = true;
0061     }
0062 }
0063 
0064 bool RunAroundHelper::fit(const bool resetHorizontalPosition, bool isRightToLeft, const QPointF &position)
0065 {
0066     Q_ASSERT(line.isValid());
0067     if (resetHorizontalPosition) {
0068         m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT;
0069         m_stayOnBaseline = false;
0070     }
0071     const qreal maxLineWidth = m_area->width();
0072     // Make sure at least some text is fitted if the basic width (page, table cell, column)
0073     // is too small
0074     if (maxLineWidth <= 0.) {
0075         // we need to make sure that something like "line.setLineWidth(0.0);" is called here to prevent
0076         // the QTextLine from being removed again and leading at a later point to crashes. It seems
0077         // following if-condition including the setNumColumns call was added to do exactly that. But
0078         // it's not clear for what the if-condition was added. In any case that condition is wrong or
0079         // incompleted cause things can still crash with m_state->layout->text().length() == 0 (see the
0080         // document attached to bug 244411).
0081 
0082         //if (m_state->layout->lineCount() > 1 || m_state->layout->text().length() > 0)
0083             line.setNumColumns(1);
0084 
0085         line.setPosition(position);
0086         return false;
0087     }
0088 
0089     // Too little width because of  wrapping is handled in the remainder of this method
0090     line.setLineWidth(maxLineWidth);
0091     const qreal maxLineHeight = line.height();
0092     const qreal maxNaturalTextWidth = line.naturalTextWidth();
0093     QRectF lineRect(position, QSizeF(maxLineWidth, maxLineHeight));
0094 
0095     if (!lineRect.isValid()) {
0096         line.setPosition(position);
0097         return false;
0098     }
0099 
0100     QRectF lineRectPart;
0101     const qreal movedDown = 10;
0102 
0103     while (!lineRectPart.isValid()) {
0104         // The line rect could be split into no further linerectpart, so we have
0105         // to move the lineRect down a bit and try again
0106         // No line rect part was enough big, to fit the line. Recreate line rect further down
0107         // (and that is divided into new line parts). Line rect is at different position to
0108         // obstructions, so new parts are completely different. if there are no obstructions, then we
0109         // have only one line part which is full line rect
0110 
0111         lineRectPart = getLineRect(lineRect, maxNaturalTextWidth);
0112         if (!lineRectPart.isValid()) {
0113             m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT;
0114             lineRect.setY(lineRect.y() + movedDown);
0115         }
0116     }
0117 
0118     if (isRightToLeft && line.naturalTextWidth() > m_textWidth) {
0119         // This can happen if spaces are added at the end of a line. Those spaces will not result in a
0120         // line-break. On left-to-right everything is fine and the spaces at the end are just not visible
0121         // but on right-to-left we need to adust the position cause spaces at the end are displayed at
0122         // the beginning and we need to make sure that doesn't result in us cutting of text at the right side.
0123         qreal diff = line.naturalTextWidth() - m_textWidth;
0124         lineRectPart.setX(lineRectPart.x() - diff);
0125     }
0126 
0127     line.setLineWidth(m_textWidth);
0128     line.setPosition(QPointF(lineRectPart.x(), lineRectPart.y()));
0129     checkEndOfLine(lineRectPart, maxNaturalTextWidth);
0130     return true;
0131 }
0132 
0133 void RunAroundHelper::validateObstructions()
0134 {
0135     m_validObstructions.clear();
0136     foreach (KoTextLayoutObstruction *obstruction, m_obstructions) {
0137         validateObstruction(obstruction);
0138     }
0139 }
0140 
0141 void RunAroundHelper::validateObstruction(KoTextLayoutObstruction *obstruction)
0142 {
0143     QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect);
0144     if (obstructionLineRect.isValid()) {
0145         m_validObstructions.append(obstruction);
0146     }
0147 }
0148 
0149 void RunAroundHelper::createLineParts()
0150 {
0151     m_lineParts.clear();
0152     if (m_validObstructions.isEmpty()) {
0153         // Add whole line rect
0154         m_lineParts.append(m_lineRect);
0155     } else {
0156         QVector<QRectF> lineParts;
0157         QRectF rightLineRect = m_lineRect;
0158         bool lastRightRectValid = false;
0159         std::sort(m_validObstructions.begin(), m_validObstructions.end(), KoTextLayoutObstruction::compareRectLeft);
0160         // Divide rect to parts, part can be invalid when obstructions are not disjunct.
0161         foreach (KoTextLayoutObstruction *validObstruction, m_validObstructions) {
0162             QRectF leftLineRect = validObstruction->getLeftLinePart(rightLineRect);
0163             lineParts.append(leftLineRect);
0164             QRectF lineRect = validObstruction->getRightLinePart(rightLineRect);
0165             if (lineRect.isValid()) {
0166                 rightLineRect = lineRect;
0167                 lastRightRectValid = true;
0168             } else {
0169                 lastRightRectValid = false;
0170             }
0171         }
0172         if (lastRightRectValid) {
0173             lineParts.append(rightLineRect);
0174         }
0175         else {
0176             lineParts.append(QRect());
0177         }
0178         Q_ASSERT(m_validObstructions.size() + 1 == lineParts.size());
0179         // Select invalid parts because of wrap.
0180         for (int i = 0; i < m_validObstructions.size(); i++) {
0181             KoTextLayoutObstruction *obstruction = m_validObstructions.at(i);
0182             if (obstruction->noTextAround()) {
0183                 lineParts.replace(i, QRectF());
0184                 lineParts.replace(i + 1, QRect());
0185             } else if (obstruction->textOnLeft()) {
0186                 lineParts.replace(i + 1, QRect());
0187             } else if (obstruction->textOnRight()) {
0188                 lineParts.replace(i, QRectF());
0189             } else if (obstruction->textOnEnoughSides()) {
0190                 QRectF leftRect = obstruction->getLeftLinePart(m_lineRect);
0191                 QRectF rightRect = obstruction->getRightLinePart(m_lineRect);
0192                 if (leftRect.width() < obstruction->runAroundThreshold()) {
0193                     lineParts.replace(i, QRectF());
0194                 }
0195                 if (rightRect.width() < obstruction->runAroundThreshold()) {
0196                     lineParts.replace(i + 1, QRectF());
0197                 }
0198             } else if (obstruction->textOnBiggerSide()) {
0199                 QRectF leftRect = obstruction->getLeftLinePart(m_lineRect);
0200                 QRectF rightRect = obstruction->getRightLinePart(m_lineRect);
0201                 if (leftRect.width() < rightRect.width()) {
0202                     lineParts.replace(i, QRectF());
0203                 } else {
0204                     lineParts.replace(i + 1, QRectF());
0205                 }
0206             }
0207         }
0208         // Filter invalid parts.
0209         foreach (const QRectF &rect, lineParts) {
0210             if (rect.isValid()) {
0211                 m_lineParts.append(rect);
0212             }
0213         }
0214     }
0215 }
0216 
0217 QRectF RunAroundHelper::minimizeHeightToLeastNeeded(const QRectF &lineRect)
0218 {
0219     Q_ASSERT(line.isValid());
0220     QRectF lineRectBase = lineRect;
0221     // Get width of one char or shape (as-char).
0222     m_textWidth = line.cursorToX(line.textStart() + 1) - line.cursorToX(line.textStart());
0223     // Make sure width is not wider than the area allows.
0224     if (m_textWidth > m_area->width()) {
0225         m_textWidth = m_area->width();
0226     }
0227     line.setLineWidth(m_textWidth);
0228     // Base linerect height on the width calculated above.
0229     lineRectBase.setHeight(line.height());
0230     return lineRectBase;
0231 }
0232 
0233 void RunAroundHelper::updateLineParts(const QRectF &lineRect)
0234 {
0235     if (m_lineRect != lineRect || m_updateValidObstructions) {
0236         m_lineRect = lineRect;
0237         m_updateValidObstructions = false;
0238         validateObstructions();
0239         createLineParts();
0240     }
0241 }
0242 
0243 QRectF RunAroundHelper::getLineRectPart()
0244 {
0245     QRectF retVal;
0246     foreach (const QRectF &lineRectPart, m_lineParts) {
0247         if (m_horizontalPosition <= lineRectPart.left() && m_textWidth <= lineRectPart.width()) {
0248             retVal = lineRectPart;
0249             break;
0250         }
0251     }
0252     return retVal;
0253 }
0254 
0255 void RunAroundHelper::setMaxTextWidth(const QRectF &minLineRectPart, const qreal leftIndent, const qreal maxNaturalTextWidth)
0256 {
0257     Q_ASSERT(line.isValid());
0258     qreal width = m_textWidth;
0259     qreal maxWidth = minLineRectPart.width() - leftIndent;
0260     qreal height;
0261     qreal maxHeight = minLineRectPart.height();
0262     qreal widthDiff = maxWidth - width;
0263 
0264     widthDiff /= 2;
0265     while (width <= maxWidth && width <= maxNaturalTextWidth && widthDiff > MIN_WIDTH) {
0266         qreal linewidth = width + widthDiff;
0267         line.setLineWidth(linewidth);
0268         height = line.height();
0269         if (height <= maxHeight) {
0270             width = linewidth;
0271             m_textWidth = width;
0272         }
0273         widthDiff /= 2;
0274     }
0275 }
0276 
0277 QRectF RunAroundHelper::getLineRect(const QRectF &lineRect, const qreal maxNaturalTextWidth)
0278 {
0279     Q_ASSERT(line.isValid());
0280 
0281     const qreal leftIndent = lineRect.left();
0282     QRectF minLineRect = minimizeHeightToLeastNeeded(lineRect);
0283     updateLineParts(minLineRect);
0284 
0285     // Get appropriate line rect part, to fit line,
0286     // using horizontal position, minimal height and width of line.
0287     QRectF lineRectPart = getLineRectPart();
0288     if (lineRectPart.isValid()) {
0289         qreal x = lineRectPart.x();
0290         qreal width = lineRectPart.width();
0291 
0292         // Limit moved the left edge, keep the indent.
0293         if (leftIndent < x) {
0294             x += leftIndent;
0295             width -= leftIndent;
0296         }
0297         line.setLineWidth(width);
0298 
0299         // Check if line rect is big enough to fit line.
0300         // Otherwise find shorter width, what means also shorter height of line.
0301         // Condition is reverted.
0302         if (line.height() > lineRectPart.height()) {
0303             setMaxTextWidth(lineRectPart, leftIndent, maxNaturalTextWidth);
0304         } else {
0305             m_textWidth = width;
0306         }
0307     }
0308     return lineRectPart;
0309 }
0310 
0311 void RunAroundHelper::checkEndOfLine(const QRectF &lineRectPart, const qreal maxNaturalTextWidth)
0312 {
0313     if (lineRectPart == m_lineParts.last() || maxNaturalTextWidth <= lineRectPart.width()) {
0314         m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT;
0315         m_stayOnBaseline = false;
0316     } else {
0317         m_horizontalPosition = lineRectPart.right();
0318         m_stayOnBaseline = true;
0319     }
0320 }