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 }