1 /* This file is part of the KDE project
2 * Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
3 * Copyright (C) 2010-2011 KO Gmbh <cbo@kogmbh.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20 #include "RunAroundHelper.h"
21
22 #include "KoTextLayoutObstruction.h"
23
24 #include "KoTextLayoutArea.h"
25
26 const qreal RIDICULOUSLY_LARGE_NEGATIVE_INDENT = -5E6;
27 #define MIN_WIDTH 0.01f
28
RunAroundHelper()29 RunAroundHelper::RunAroundHelper()
30 {
31 m_lineRect = QRectF();
32 m_updateValidObstructions = false;
33 m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT;
34 m_stayOnBaseline = false;
35 }
36
setLine(KoTextLayoutArea * area,const QTextLine & l)37 void RunAroundHelper::setLine(KoTextLayoutArea *area, const QTextLine &l) {
38 m_area = area;
39 line = l;
40 }
41
setObstructions(const QList<KoTextLayoutObstruction * > & obstructions)42 void RunAroundHelper::setObstructions(const QList<KoTextLayoutObstruction*> &obstructions)
43 {
44 m_obstructions = obstructions;
45 }
46
stayOnBaseline() const47 bool RunAroundHelper::stayOnBaseline() const
48 {
49 return m_stayOnBaseline;
50 }
51
updateObstruction(KoTextLayoutObstruction * obstruction)52 void RunAroundHelper::updateObstruction(KoTextLayoutObstruction *obstruction)
53 {
54 QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect);
55 if (obstructionLineRect.isValid()) {
56 m_updateValidObstructions = true;
57 }
58 }
59
fit(const bool resetHorizontalPosition,bool isRightToLeft,const QPointF & position)60 bool RunAroundHelper::fit(const bool resetHorizontalPosition, bool isRightToLeft, const QPointF &position)
61 {
62 Q_ASSERT(line.isValid());
63 if (resetHorizontalPosition) {
64 m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT;
65 m_stayOnBaseline = false;
66 }
67 const qreal maxLineWidth = m_area->width();
68 // Make sure at least some text is fitted if the basic width (page, table cell, column)
69 // is too small
70 if (maxLineWidth <= 0.) {
71 // we need to make sure that something like "line.setLineWidth(0.0);" is called here to prevent
72 // the QTextLine from being removed again and leading at a later point to crashes. It seems
73 // following if-condition including the setNumColumns call was added to do exactly that. But
74 // it's not clear for what the if-condition was added. In any case that condition is wrong or
75 // incompleted cause things can still crash with m_state->layout->text().length() == 0 (see the
76 // document attached to bug 244411).
77
78 //if (m_state->layout->lineCount() > 1 || m_state->layout->text().length() > 0)
79 line.setNumColumns(1);
80
81 line.setPosition(position);
82 return false;
83 }
84
85 // Too little width because of wrapping is handled in the remainder of this method
86 line.setLineWidth(maxLineWidth);
87 const qreal maxLineHeight = line.height();
88 const qreal maxNaturalTextWidth = line.naturalTextWidth();
89 QRectF lineRect(position, QSizeF(maxLineWidth, maxLineHeight));
90 QRectF lineRectPart;
91 qreal movedDown = 10;
92
93 while (!lineRectPart.isValid()) {
94 // The line rect could be split into no further linerectpart, so we have
95 // to move the lineRect down a bit and try again
96 // No line rect part was enough big, to fit the line. Recreate line rect further down
97 // (and that is divided into new line parts). Line rect is at different position to
98 // obstructions, so new parts are completely different. if there are no obstructions, then we
99 // have only one line part which is full line rect
100
101 lineRectPart = getLineRect(lineRect, maxNaturalTextWidth);
102 if (!lineRectPart.isValid()) {
103 m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT;
104 lineRect = QRectF(position, QSizeF(maxLineWidth, maxLineHeight));
105 lineRect.setY(lineRect.y() + movedDown);
106 movedDown += 10;
107 }
108 }
109
110 if (isRightToLeft && line.naturalTextWidth() > m_textWidth) {
111 // This can happen if spaces are added at the end of a line. Those spaces will not result in a
112 // line-break. On left-to-right everything is fine and the spaces at the end are just not visible
113 // but on right-to-left we need to adjust the position cause spaces at the end are displayed at
114 // the beginning and we need to make sure that doesn't result in us cutting of text at the right side.
115 qreal diff = line.naturalTextWidth() - m_textWidth;
116 lineRectPart.setX(lineRectPart.x() - diff);
117 }
118
119 line.setLineWidth(m_textWidth);
120 line.setPosition(QPointF(lineRectPart.x(), lineRectPart.y()));
121 checkEndOfLine(lineRectPart, maxNaturalTextWidth);
122 return true;
123 }
124
validateObstructions()125 void RunAroundHelper::validateObstructions()
126 {
127 m_validObstructions.clear();
128 foreach (KoTextLayoutObstruction *obstruction, m_obstructions) {
129 validateObstruction(obstruction);
130 }
131 }
132
validateObstruction(KoTextLayoutObstruction * obstruction)133 void RunAroundHelper::validateObstruction(KoTextLayoutObstruction *obstruction)
134 {
135 QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect);
136 if (obstructionLineRect.isValid()) {
137 m_validObstructions.append(obstruction);
138 }
139 }
140
createLineParts()141 void RunAroundHelper::createLineParts()
142 {
143 m_lineParts.clear();
144 if (m_validObstructions.isEmpty()) {
145 // Add whole line rect
146 m_lineParts.append(m_lineRect);
147 } else {
148 QList<QRectF> lineParts;
149 QRectF rightLineRect = m_lineRect;
150 bool lastRightRectValid = false;
151 std::sort(m_validObstructions.begin(), m_validObstructions.end(), KoTextLayoutObstruction::compareRectLeft);
152 // Divide rect to parts, part can be invalid when obstructions are not disjunct.
153 foreach (KoTextLayoutObstruction *validObstruction, m_validObstructions) {
154 QRectF leftLineRect = validObstruction->getLeftLinePart(rightLineRect);
155 lineParts.append(leftLineRect);
156 QRectF lineRect = validObstruction->getRightLinePart(rightLineRect);
157 if (lineRect.isValid()) {
158 rightLineRect = lineRect;
159 lastRightRectValid = true;
160 } else {
161 lastRightRectValid = false;
162 }
163 }
164 if (lastRightRectValid) {
165 lineParts.append(rightLineRect);
166 }
167 else {
168 lineParts.append(QRect());
169 }
170 Q_ASSERT(m_validObstructions.size() + 1 == lineParts.size());
171 // Select invalid parts because of wrap.
172 for (int i = 0; i < m_validObstructions.size(); i++) {
173 KoTextLayoutObstruction *obstruction = m_validObstructions.at(i);
174 if (obstruction->noTextAround()) {
175 lineParts.replace(i, QRectF());
176 lineParts.replace(i + 1, QRect());
177 } else if (obstruction->textOnLeft()) {
178 lineParts.replace(i + 1, QRect());
179 } else if (obstruction->textOnRight()) {
180 lineParts.replace(i, QRectF());
181 } else if (obstruction->textOnEnoughSides()) {
182 QRectF leftRect = obstruction->getLeftLinePart(m_lineRect);
183 QRectF rightRect = obstruction->getRightLinePart(m_lineRect);
184 if (leftRect.width() < obstruction->runAroundThreshold()) {
185 lineParts.replace(i, QRectF());
186 }
187 if (rightRect.width() < obstruction->runAroundThreshold()) {
188 lineParts.replace(i + 1, QRectF());
189 }
190 } else if (obstruction->textOnBiggerSide()) {
191 QRectF leftRect = obstruction->getLeftLinePart(m_lineRect);
192 QRectF rightRect = obstruction->getRightLinePart(m_lineRect);
193 if (leftRect.width() < rightRect.width()) {
194 lineParts.replace(i, QRectF());
195 } else {
196 lineParts.replace(i + 1, QRectF());
197 }
198 }
199 }
200 // Filter invalid parts.
201 foreach (const QRectF &rect, lineParts) {
202 if (rect.isValid()) {
203 m_lineParts.append(rect);
204 }
205 }
206 }
207 }
208
minimizeHeightToLeastNeeded(const QRectF & lineRect)209 QRectF RunAroundHelper::minimizeHeightToLeastNeeded(const QRectF &lineRect)
210 {
211 Q_ASSERT(line.isValid());
212 QRectF lineRectBase = lineRect;
213 // Get width of one char or shape (as-char).
214 m_textWidth = line.cursorToX(line.textStart() + 1) - line.cursorToX(line.textStart());
215 // Make sure width is not wider than the area allows.
216 if (m_textWidth > m_area->width()) {
217 m_textWidth = m_area->width();
218 }
219 line.setLineWidth(m_textWidth);
220 // Base linerect height on the width calculated above.
221 lineRectBase.setHeight(line.height());
222 return lineRectBase;
223 }
224
updateLineParts(const QRectF & lineRect)225 void RunAroundHelper::updateLineParts(const QRectF &lineRect)
226 {
227 if (m_lineRect != lineRect || m_updateValidObstructions) {
228 m_lineRect = lineRect;
229 m_updateValidObstructions = false;
230 validateObstructions();
231 createLineParts();
232 }
233 }
234
getLineRectPart()235 QRectF RunAroundHelper::getLineRectPart()
236 {
237 QRectF retVal;
238 foreach (const QRectF &lineRectPart, m_lineParts) {
239 if (m_horizontalPosition <= lineRectPart.left() && m_textWidth <= lineRectPart.width()) {
240 retVal = lineRectPart;
241 break;
242 }
243 }
244 return retVal;
245 }
246
setMaxTextWidth(const QRectF & minLineRectPart,const qreal leftIndent,const qreal maxNaturalTextWidth)247 void RunAroundHelper::setMaxTextWidth(const QRectF &minLineRectPart, const qreal leftIndent, const qreal maxNaturalTextWidth)
248 {
249 Q_ASSERT(line.isValid());
250 qreal width = m_textWidth;
251 qreal maxWidth = minLineRectPart.width() - leftIndent;
252 qreal height;
253 qreal maxHeight = minLineRectPart.height();
254 qreal widthDiff = maxWidth - width;
255
256 widthDiff /= 2;
257 while (width <= maxWidth && width <= maxNaturalTextWidth && widthDiff > MIN_WIDTH) {
258 qreal linewidth = width + widthDiff;
259 line.setLineWidth(linewidth);
260 height = line.height();
261 if (height <= maxHeight) {
262 width = linewidth;
263 m_textWidth = width;
264 }
265 widthDiff /= 2;
266 }
267 }
268
getLineRect(const QRectF & lineRect,const qreal maxNaturalTextWidth)269 QRectF RunAroundHelper::getLineRect(const QRectF &lineRect, const qreal maxNaturalTextWidth)
270 {
271 Q_ASSERT(line.isValid());
272
273 const qreal leftIndent = lineRect.left();
274 QRectF minLineRect = minimizeHeightToLeastNeeded(lineRect);
275 updateLineParts(minLineRect);
276
277 // Get appropriate line rect part, to fit line,
278 // using horizontal position, minimal height and width of line.
279 QRectF lineRectPart = getLineRectPart();
280 if (lineRectPart.isValid()) {
281 qreal x = lineRectPart.x();
282 qreal width = lineRectPart.width();
283
284 // Limit moved the left edge, keep the indent.
285 if (leftIndent < x) {
286 x += leftIndent;
287 width -= leftIndent;
288 }
289 line.setLineWidth(width);
290
291 // Check if line rect is big enough to fit line.
292 // Otherwise find shorter width, what means also shorter height of line.
293 // Condition is reverted.
294 if (line.height() > lineRectPart.height()) {
295 setMaxTextWidth(lineRectPart, leftIndent, maxNaturalTextWidth);
296 } else {
297 m_textWidth = width;
298 }
299 }
300 return lineRectPart;
301 }
302
checkEndOfLine(const QRectF & lineRectPart,const qreal maxNaturalTextWidth)303 void RunAroundHelper::checkEndOfLine(const QRectF &lineRectPart, const qreal maxNaturalTextWidth)
304 {
305 if (lineRectPart == m_lineParts.last() || maxNaturalTextWidth <= lineRectPart.width()) {
306 m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT;
307 m_stayOnBaseline = false;
308 } else {
309 m_horizontalPosition = lineRectPart.right();
310 m_stayOnBaseline = true;
311 }
312 }
313