KGuiAddons

kwordwrap.cpp
1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 2001 David Faure <faure@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "kwordwrap.h"
8
9#include <QList>
10#include <QPainter>
11
12class KWordWrapPrivate : public QSharedData
13{
14public:
15 QRect m_constrainingRect;
16 QList<int> m_breakPositions;
17 QList<int> m_lineWidths;
18 QRect m_boundingRect;
19 QString m_text;
20};
21
23 : d(new KWordWrapPrivate)
24{
25 d->m_constrainingRect = r;
26}
27
28KWordWrap KWordWrap::formatText(QFontMetrics &fm, const QRect &r, int /*flags*/, const QString &str, int len)
29{
30 KWordWrap kw(r);
31 // The wordwrap algorithm
32 // The variable names and the global shape of the algorithm are inspired
33 // from QTextFormatterBreakWords::format().
34 // qDebug() << "KWordWrap::formatText " << str << " r=" << r.x() << "," << r.y() << " " << r.width() << "x" << r.height();
35 int height = fm.height();
36 if (len == -1) {
37 kw.d->m_text = str;
38 } else {
39 kw.d->m_text = str.left(len);
40 }
41 if (len == -1) {
42 len = str.length();
43 }
44 int lastBreak = -1;
45 int lineWidth = 0;
46 int x = 0;
47 int y = 0;
48 int w = r.width();
49 int textwidth = 0;
50 bool isBreakable = false;
51 bool wasBreakable = false; // value of isBreakable for last char (i-1)
52 bool isParens = false; // true if one of ({[
53 bool wasParens = false; // value of isParens for last char (i-1)
54 QString inputString = str;
55
56 for (int i = 0; i < len; ++i) {
57 const QChar c = inputString.at(i);
58 const int ww = fm.horizontalAdvance(c);
59
60 isParens = (c == QLatin1Char('(') //
61 || c == QLatin1Char('[') //
62 || c == QLatin1Char('{'));
63 // isBreakable is true when we can break _after_ this character.
64 isBreakable = (c.isSpace() || c.isPunct() || c.isSymbol()) & !isParens;
65
66 // Special case for '(', '[' and '{': we want to break before them
67 if (!isBreakable && i < len - 1) {
68 const QChar nextc = inputString.at(i + 1); // look at next char
69 isBreakable = (nextc == QLatin1Char('(') //
70 || nextc == QLatin1Char('[') //
71 || nextc == QLatin1Char('{'));
72 }
73 // Special case for '/': after normal chars it's breakable (e.g. inside a path),
74 // but after another breakable char it's not (e.g. "mounted at /foo")
75 // Same thing after a parenthesis (e.g. "dfaure [/fool]")
76 if (c == QLatin1Char('/') && (wasBreakable || wasParens)) {
77 isBreakable = false;
78 }
79
80 /*qDebug() << "c='" << QString(c) << "' i=" << i << "/" << len
81 << " x=" << x << " ww=" << ww << " w=" << w
82 << " lastBreak=" << lastBreak << " isBreakable=" << isBreakable << endl;*/
83 int breakAt = -1;
84 if (x + ww > w && lastBreak != -1) { // time to break and we know where
85 breakAt = lastBreak;
86 }
87 if (x + ww > w - 4 && lastBreak == -1) { // time to break but found nowhere [-> break here]
88 breakAt = i;
89 }
90 if (i == len - 2 && x + ww + fm.horizontalAdvance(inputString.at(i + 1)) > w) { // don't leave the last char alone
91 breakAt = lastBreak == -1 ? i - 1 : lastBreak;
92 }
93 if (c == QLatin1Char('\n')) { // Forced break here
94 if (breakAt == -1 && lastBreak != -1) { // only break if not already breaking
95 breakAt = i - 1;
96 lastBreak = -1;
97 }
98 // remove the line feed from the string
99 kw.d->m_text.remove(i, 1);
100 inputString.remove(i, 1);
101 len--;
102 }
103 if (breakAt != -1) {
104 // qDebug() << "KWordWrap::formatText breaking after " << breakAt;
105 kw.d->m_breakPositions.append(breakAt);
106 int thisLineWidth = lastBreak == -1 ? x + ww : lineWidth;
107 kw.d->m_lineWidths.append(thisLineWidth);
108 textwidth = qMax(textwidth, thisLineWidth);
109 x = 0;
110 y += height;
111 wasBreakable = true;
112 wasParens = false;
113 if (lastBreak != -1) {
114 // Breakable char was found, restart from there
115 i = lastBreak;
116 lastBreak = -1;
117 continue;
118 }
119 } else if (isBreakable) {
120 lastBreak = i;
121 lineWidth = x + ww;
122 }
123 x += ww;
124 wasBreakable = isBreakable;
125 wasParens = isParens;
126 }
127 textwidth = qMax(textwidth, x);
128 kw.d->m_lineWidths.append(x);
129 y += height;
130 // qDebug() << "KWordWrap::formatText boundingRect:" << r.x() << "," << r.y() << " " << textwidth << "x" << y;
131 if (r.height() >= 0 && y > r.height()) {
132 textwidth = r.width();
133 }
134 int realY = y;
135 if (r.height() >= 0) {
136 while (realY > r.height()) {
137 realY -= height;
138 }
139 realY = qMax(realY, 0);
140 }
141 kw.d->m_boundingRect.setRect(0, 0, textwidth, realY);
142 return kw;
143}
144
148
150 : d(other.d)
151{
152}
153
155{
156 d = other.d;
157 return *this;
158}
159
161{
162 const QStringView strView(d->m_text);
163 // We use the calculated break positions to insert '\n' into the string
164 QString ws;
165 int start = 0;
166 for (int i = 0; i < d->m_breakPositions.count(); ++i) {
167 int end = d->m_breakPositions.at(i);
168 ws += strView.mid(start, end - start + 1);
169 ws += QLatin1Char('\n');
170 start = end + 1;
171 }
172 ws += strView.mid(start);
173 return ws;
174}
175
177{
178 if (d->m_breakPositions.isEmpty()) {
179 return d->m_text;
180 }
181
182 QString ts = d->m_text.left(d->m_breakPositions.first() + 1);
183 if (dots) {
184 ts += QLatin1String("...");
185 }
186 return ts;
187}
188
189static QColor mixColors(double p1, QColor c1, QColor c2)
190{
191 return QColor(int(c1.red() * p1 + c2.red() * (1.0 - p1)), //
192 int(c1.green() * p1 + c2.green() * (1.0 - p1)), //
193 int(c1.blue() * p1 + c2.blue() * (1.0 - p1)));
194}
195
196void KWordWrap::drawFadeoutText(QPainter *p, int x, int y, int maxW, const QString &t)
197{
198 QFontMetrics fm = p->fontMetrics();
199 QColor bgColor = p->background().color();
200 QColor textColor = p->pen().color();
201
202 if ((fm.boundingRect(t).width() > maxW) && (t.length() > 1)) {
203 int tl = 0;
204 int w = 0;
205 while (tl < t.length()) {
206 w += fm.horizontalAdvance(t.at(tl));
207 if (w >= maxW) {
208 break;
209 }
210 tl++;
211 }
212
213 int n = qMin(tl, 3);
214 if (t.isRightToLeft()) {
215 x += maxW; // start from the right side for RTL string
216 if (tl > 3) {
217 x -= fm.horizontalAdvance(t.left(tl - 3));
218 p->drawText(x, y, t.left(tl - 3));
219 }
220 for (int i = 0; i < n; i++) {
221 p->setPen(mixColors(0.70 - i * 0.25, textColor, bgColor));
222 QString s(t.at(tl - n + i));
223 x -= fm.horizontalAdvance(s);
224 p->drawText(x, y, s);
225 }
226 } else {
227 if (tl > 3) {
228 p->drawText(x, y, t.left(tl - 3));
229 x += fm.horizontalAdvance(t.left(tl - 3));
230 }
231 for (int i = 0; i < n; i++) {
232 p->setPen(mixColors(0.70 - i * 0.25, textColor, bgColor));
233 QString s(t.at(tl - n + i));
234 p->drawText(x, y, s);
235 x += fm.horizontalAdvance(s);
236 }
237 }
238 } else {
239 p->drawText(x, y, t);
240 }
241}
242
243void KWordWrap::drawTruncateText(QPainter *p, int x, int y, int maxW, const QString &t)
244{
245 QString tmpText = p->fontMetrics().elidedText(t, Qt::ElideRight, maxW);
246 p->drawText(x, y, tmpText);
247}
248
249void KWordWrap::drawText(QPainter *painter, int textX, int textY, int flags) const
250{
251 // qDebug() << "KWordWrap::drawText text=" << wrappedString() << " x=" << textX << " y=" << textY;
252 // We use the calculated break positions to draw the text line by line using QPainter
253 int start = 0;
254 int y = 0;
255 QFontMetrics fm = painter->fontMetrics();
256 int height = fm.height(); // line height
257 int ascent = fm.ascent();
258 int maxwidth = d->m_boundingRect.width();
259 int i;
260 int lwidth = 0;
261 int end = 0;
262 for (i = 0; i < d->m_breakPositions.count(); ++i) {
263 // if this is the last line, leave the loop
264 if (d->m_constrainingRect.height() >= 0 //
265 && ((y + 2 * height) > d->m_constrainingRect.height())) {
266 break;
267 }
268 end = d->m_breakPositions.at(i);
269 lwidth = d->m_lineWidths.at(i);
270 int x = textX;
271 if (flags & Qt::AlignHCenter) {
272 x += (maxwidth - lwidth) / 2;
273 } else if (flags & Qt::AlignRight) {
274 x += maxwidth - lwidth;
275 }
276 painter->drawText(x, textY + y + ascent, d->m_text.mid(start, end - start + 1));
277 y += height;
278 start = end + 1;
279 }
280
281 // Draw the last line
282 lwidth = d->m_lineWidths.last();
283 int x = textX;
284 if (flags & Qt::AlignHCenter) {
285 x += (maxwidth - lwidth) / 2;
286 } else if (flags & Qt::AlignRight) {
287 x += maxwidth - lwidth;
288 }
289 if ((d->m_constrainingRect.height() < 0) || ((y + height) <= d->m_constrainingRect.height())) {
290 if (i == d->m_breakPositions.count()) {
291 painter->drawText(x, textY + y + ascent, d->m_text.mid(start));
292 } else if (flags & FadeOut) {
293 drawFadeoutText(painter, textX, textY + y + ascent, d->m_constrainingRect.width(), d->m_text.mid(start));
294 } else if (flags & Truncate) {
295 drawTruncateText(painter, textX, textY + y + ascent, d->m_constrainingRect.width(), d->m_text.mid(start));
296 } else {
297 painter->drawText(x, textY + y + ascent, d->m_text.mid(start));
298 }
299 }
300}
301
303{
304 return d->m_boundingRect;
305}
Word-wrap algorithm that takes into account beautifulness ;)
Definition kwordwrap.h:43
QRect boundingRect() const
KWordWrap & operator=(const KWordWrap &other)
Assignment operator.
static KWordWrap formatText(QFontMetrics &fm, const QRect &r, int flags, const QString &str, int len=-1)
Main method for wrapping text.
Definition kwordwrap.cpp:28
static void drawFadeoutText(QPainter *p, int x, int y, int maxW, const QString &t)
Draws the string t at the given coordinates, if it does not fit into maxW the text will be faded out.
KWordWrap(const KWordWrap &other)
Copy constructor.
QString truncatedString(bool dots=true) const
static void drawTruncateText(QPainter *p, int x, int y, int maxW, const QString &t)
Draws the string t at the given coordinates, if it does not fit into maxW the text will be truncated.
~KWordWrap()
Destructor.
void drawText(QPainter *painter, int x, int y, int flags=Qt::AlignLeft) const
Draw the text that has been previously wrapped, at position x,y.
QString wrappedString() const
Q_SCRIPTABLE Q_NOREPLY void start()
const QColor & color() const const
bool isPunct(char32_t ucs4)
bool isSpace(char32_t ucs4)
bool isSymbol(char32_t ucs4)
int blue() const const
int green() const const
int red() const const
int ascent() const const
QRect boundingRect(QChar ch) const const
QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const const
int height() const const
int horizontalAdvance(QChar ch) const const
const QBrush & background() const const
void drawText(const QPoint &position, const QString &text)
QFontMetrics fontMetrics() const const
const QPen & pen() const const
void setPen(Qt::PenStyle style)
QColor color() const const
int height() const const
int width() const const
const QChar at(qsizetype position) const const
bool isRightToLeft() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QStringView mid(qsizetype start, qsizetype length) const const
AlignHCenter
ElideRight
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:40 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.