1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5
6 #include "mumble_pch.hpp"
7
8 #include "OverlayText.h"
9
10 #include "Global.h"
11
BasepointPixmap()12 BasepointPixmap::BasepointPixmap() :
13 qpBasePoint(0, 0),
14 iAscent(-1),
15 iDescent(-1) { }
16
BasepointPixmap(const QPixmap & pm)17 BasepointPixmap::BasepointPixmap(const QPixmap& pm) :
18 QPixmap(pm),
19 qpBasePoint(0, pm.height()),
20 iAscent(-1),
21 iDescent(-1) { }
22
BasepointPixmap(int w,int h)23 BasepointPixmap::BasepointPixmap(int w, int h) :
24 QPixmap(w, h),
25 qpBasePoint(0, h),
26 iAscent(-1),
27 iDescent(-1) { }
28
BasepointPixmap(int w,int h,const QPoint & bp)29 BasepointPixmap::BasepointPixmap(int w, int h, const QPoint& bp) :
30 QPixmap(w, h),
31 qpBasePoint(bp),
32 iAscent(-1),
33 iDescent(-1) { }
34
OverlayTextLine(const QString & s,const QFont & f)35 OverlayTextLine::OverlayTextLine(const QString& s, const QFont& f)
36 : fEdgeFactor(0.05f)
37 , qsText(s)
38 , qfFont(f)
39 , fXCorrection(0.0)
40 , fYCorrection(0.0)
41 , iCurWidth(-1)
42 , iCurHeight(-1)
43 , fBaseliningThreshold(0.5f)
44 , bElided(false) {
45
46 QFontMetrics fm(f);
47 fAscent = static_cast<float>(fm.ascent());
48 fDescent = static_cast<float>(fm.descent());
49 fEdge = qMax(static_cast<float>(f.pointSizeF()) * fEdgeFactor, 1.0f);
50 }
51
render(int w,int h,const QColor & col,const QPoint & bp) const52 BasepointPixmap OverlayTextLine::render(int w, int h, const QColor& col, const QPoint& bp) const {
53 BasepointPixmap img(w, h, bp);
54 img.fill(Qt::transparent);
55
56 QPainter imgp(&img);
57 imgp.setRenderHint(QPainter::Antialiasing);
58 imgp.setRenderHint(QPainter::TextAntialiasing);
59 imgp.setBackground(QColor(0,0,0,0));
60 imgp.setCompositionMode(QPainter::CompositionMode_SourceOver);
61
62 QColor qc(col);
63 qc.setAlpha(255);
64
65 imgp.setBrush(qc);
66 imgp.setPen(QPen(Qt::black, fEdge, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
67 imgp.drawPath(qpp);
68
69 imgp.setPen(Qt::NoPen);
70 imgp.drawPath(qpp);
71
72 img.iAscent = iroundf(fAscent + 0.5f);
73 img.iDescent = iroundf(fDescent + 0.5f);
74 return img;
75 }
76
createPixmap(QColor col)77 BasepointPixmap OverlayTextLine::createPixmap(QColor col) {
78 if (qsText.isEmpty()) {
79 return BasepointPixmap();
80 }
81
82 QRectF qr;
83 if (qpp.isEmpty()) {
84 qpp.addText(0.0f, fAscent, qfFont, qsText);
85 qr = qpp.controlPointRect();
86
87 // fit into (0,0)-based coordinates
88 fXCorrection = 0.0f;
89 fYCorrection = 0.0f;
90
91 if (qr.left() < fEdge) {
92 fXCorrection = fEdge - static_cast<float>(qr.left());
93 }
94
95 if (qr.top() < fEdge) {
96 fYCorrection = fEdge - static_cast<float>(qr.top());
97 }
98
99 QMatrix correction;
100 correction.translate(fXCorrection, fYCorrection);
101 qpp = correction.map(qpp);
102 }
103
104 qr = qpp.controlPointRect();
105
106 return render(
107 iroundf(qr.right() + 2.0f*fEdge + 0.5f),
108 iroundf(qr.bottom() + 2.0f*fEdge + 0.5f),
109 col,
110 QPoint(iroundf(fXCorrection + 0.5f), iroundf(fYCorrection + fAscent + 0.5f))
111 );
112 }
113
createPixmap(unsigned int maxwidth,unsigned int height,QColor col)114 BasepointPixmap OverlayTextLine::createPixmap(unsigned int maxwidth, unsigned int height, QColor col) {
115 float twice_edge = 2.0f * fEdge;
116
117 if (! height || ! maxwidth)
118 return BasepointPixmap();
119
120 if (qpp.isEmpty() || iCurWidth > static_cast<int>(maxwidth) || iCurHeight != static_cast<int>(height) || (static_cast<int>(maxwidth) > iCurWidth && bElided)) {
121 QFont f = qfFont;
122 QFontMetrics fm(f);
123
124 // fit the font into a bounding box with padding
125 float ps = static_cast<float>(f.pointSizeF());
126 float f_ad = static_cast<float>(fm.ascent() + fm.descent()+1) / ps;
127
128 float pointsize = static_cast<float>(height) / (f_ad + 2.0f*fEdgeFactor);
129
130 if (fEdgeFactor * ps > 1.0f) {
131 pointsize = static_cast<float>(height-2) / f_ad;
132 }
133
134 if (pointsize <= 0.0f) {
135 return BasepointPixmap();
136 }
137
138 f.setPointSizeF(pointsize);
139 setFont(f);
140 fm = QFontMetrics(f);
141 twice_edge = 2.0f * fEdge;
142
143 if (!qpp.isEmpty()) {
144 qpp = QPainterPath();
145 }
146
147 // calculate text metrics for eliding and scaling
148 QRectF bb;
149 qpp.addText(0.0f, 0.0f, f, qsText);
150 bb = qpp.controlPointRect();
151
152 qreal effective_ascent = -bb.top();
153 qreal effective_descent = bb.bottom();
154 float scale = 1.0f;
155 bool keep_baseline = true;
156 if (effective_descent > fDescent || effective_ascent > fAscent) {
157 qreal scale_ascent = effective_ascent > 0.0f ? fAscent / effective_ascent : 1.0f;
158 qreal scale_descent = effective_descent > 0.0f ? fDescent / effective_descent : 1.0f;
159 scale = static_cast<float>(qMin(scale_ascent, scale_descent));
160
161 if (scale < fBaseliningThreshold) {
162 float text_height = static_cast<float>(bb.height()) + twice_edge;
163 scale = static_cast<float>(height) / text_height;
164 keep_baseline = false;
165 }
166
167 qWarning() << QString(QLatin1String("Text \"%1\" did not fit (+%2/-%3): (+%4/-%5). Scaling to %6.")).arg(qsText).arg(fAscent).arg(fDescent).arg(effective_ascent).arg(effective_descent).arg(scale);
168 }
169
170 // eliding by previously calculated width
171 if ((bb.width()*scale) + twice_edge > maxwidth) {
172 int eliding_width = iroundf((static_cast<float>(maxwidth) / scale) - twice_edge + 0.5f);
173 QString str = fm.elidedText(qsText, Qt::ElideRight, eliding_width);
174
175 // use ellipsis as shortest possible string
176 if (str.trimmed().isEmpty()) {
177 str = QString(QChar(0x2026));
178 }
179
180 qpp = QPainterPath();
181 qpp.addText(0.0f, 0.0f, f, str);
182 bb = qpp.controlPointRect();
183 bElided = true;
184 } else {
185 bElided = false;
186 }
187
188 // translation to "pixmap space":
189 QMatrix correction;
190 // * adjust left edge
191 correction.translate(-bb.x() + fEdge, 0.0f);
192 // * scale overly high text (still on baseline)
193 correction.scale(scale, scale);
194
195 if (keep_baseline) {
196 // * translate down to baseline
197 correction.translate(0.0f, (fAscent + fEdge) / scale);
198 } else {
199 // * translate into bounding box
200 correction.translate(0.0f, -bb.top() + fEdge);
201 }
202
203 qpp = correction.map(qpp);
204 iCurWidth = iroundf(bb.width() * scale + 0.5f);
205 iCurHeight = height;
206 }
207
208 QRectF qr = qpp.controlPointRect();
209
210 return render(
211 iroundf(qr.width() + twice_edge + 0.5f),
212 iroundf(fAscent + fDescent + twice_edge + 0.5f),
213 col,
214 QPoint(0, iroundf(fAscent + fEdge + 0.5f))
215 );
216 }
217
setFont(const QFont & font)218 void OverlayTextLine::setFont(const QFont& font) {
219 qfFont = font;
220 qpp = QPainterPath();
221 QFontMetrics fm(font);
222 fAscent = static_cast<float>(fm.ascent()+1);
223 fDescent = static_cast<float>(fm.descent()+1);
224 fEdge = qMax(static_cast<float>(font.pointSizeF()) * fEdgeFactor, 1.0f);
225 }
226
setEdge(float ew)227 void OverlayTextLine::setEdge(float ew) {
228 fEdge = ew;
229 qpp = QPainterPath();
230 }
231