1 /*
2 * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
3 *
4 * This file is part of the KD Chart library.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "KChartTextLabelCache.h"
21
22 #include <cmath>
23
24 #include <QtDebug>
25 #include <QImage>
26 #include <QPixmap>
27 #include <QPainter>
28 #include <QApplication>
29
30 #ifndef NDEBUG
31 int HitCount = 0;
32 int MissCount = 0;
33 #define INC_HIT_COUNT { ++HitCount; }
34 #define INC_MISS_COUNT { ++MissCount; }
35 #define DUMP_CACHE_STATS \
36 if ( HitCount != 0 && MissCount != 0 ) { \
37 int total = HitCount + MissCount; \
38 qreal hitQuote = ( 1.0 * HitCount ) / total; \
39 qDebug() << "PrerenderedLabel dtor: hits/misses/total:" \
40 << HitCount << "/" << MissCount << "/" << total \
41 << "(" << 100 * hitQuote << "% hits)"; \
42 }
43 #else
44 #define INC_HIT_COUNT
45 #define INC_MISS_COUNT
46 #define DUMP_CACHE_STATS
47 #endif
48
PrerenderedElement()49 PrerenderedElement::PrerenderedElement()
50 : m_referencePoint( KChartEnums::PositionNorthWest )
51 {
52 }
53
setPosition(const QPointF & position)54 void PrerenderedElement::setPosition( const QPointF& position )
55 { // this does not invalidate the element
56 m_position = position;
57 }
58
position() const59 const QPointF& PrerenderedElement::position() const
60 {
61 return m_position;
62 }
63
setReferencePoint(KChartEnums::PositionValue point)64 void PrerenderedElement::setReferencePoint( KChartEnums::PositionValue point )
65 { // this does not invalidate the element
66 m_referencePoint = point;
67 }
68
referencePoint() const69 KChartEnums::PositionValue PrerenderedElement::referencePoint() const
70 {
71 return m_referencePoint;
72 }
73
PrerenderedLabel()74 PrerenderedLabel::PrerenderedLabel()
75 : PrerenderedElement()
76 , m_dirty( true )
77 , m_font( qApp->font() )
78 , m_brush( Qt::black )
79 , m_pen( Qt::black ) // do not use anything invisible
80 , m_angle( 0.0 )
81 {
82 }
83
~PrerenderedLabel()84 PrerenderedLabel::~PrerenderedLabel()
85 {
86 DUMP_CACHE_STATS;
87 }
88
invalidate() const89 void PrerenderedLabel::invalidate() const
90 {
91 m_dirty = true;
92 }
93
setFont(const QFont & font)94 void PrerenderedLabel::setFont( const QFont& font )
95 {
96 m_font = font;
97 invalidate();
98 }
99
font() const100 const QFont& PrerenderedLabel::font() const
101 {
102 return m_font;
103 }
104
setText(const QString & text)105 void PrerenderedLabel::setText( const QString& text )
106 {
107 m_text = text;
108 invalidate();
109 }
110
text() const111 const QString& PrerenderedLabel::text() const
112 {
113 return m_text;
114 }
115
setBrush(const QBrush & brush)116 void PrerenderedLabel::setBrush( const QBrush& brush )
117 {
118 m_brush = brush;
119 invalidate();
120 }
121
brush() const122 const QBrush& PrerenderedLabel::brush() const
123 {
124 return m_brush;
125 }
126
setAngle(qreal angle)127 void PrerenderedLabel::setAngle( qreal angle )
128 {
129 m_angle = angle;
130 invalidate();
131 }
132
angle() const133 qreal PrerenderedLabel::angle() const
134 {
135 return m_angle;
136 }
137
pixmap() const138 const QPixmap& PrerenderedLabel::pixmap() const
139 {
140 if ( m_dirty ) {
141 INC_MISS_COUNT;
142 paint();
143 } else {
144 INC_HIT_COUNT;
145 }
146 return m_pixmap;
147 }
148
paint() const149 void PrerenderedLabel::paint() const
150 {
151 // FIXME find a better value using font metrics of text (this
152 // requires finding the diameter of the circle formed by rotating
153 // the bounding rect around the center):
154 const int Width = 1000;
155 const int Height = Width;
156
157 QRectF boundingRect;
158 const QColor FullTransparent( 255, 255, 255, 0 );
159 #ifdef Q_WS_X11
160 QImage pixmap( Width, Height, QImage::Format_ARGB32_Premultiplied );
161 qWarning() << "PrerenderedLabel::paint: using QImage for prerendered labels "
162 << "to work around XRender/Qt4 bug.";
163 #else
164 QPixmap pixmap( Width, Height );
165 #endif
166 // pixmap.fill( FullTransparent );
167 {
168 static const QPointF Center ( 0.0, 0.0 );
169 QPointF textBottomRight;
170 QPainter painter( &pixmap );
171 painter.setRenderHint(QPainter::TextAntialiasing, true );
172 painter.setRenderHint(QPainter::Antialiasing, true );
173
174 // QImage (X11 workaround) does not have fill():
175 painter.setPen( FullTransparent );
176 painter.setBrush( FullTransparent );
177 QPainter::CompositionMode mode = painter.compositionMode();
178 painter.setCompositionMode( QPainter::CompositionMode_Clear );
179 painter.drawRect( 0, 0, Width, Height );
180 painter.setCompositionMode( mode );
181
182 QMatrix matrix;
183 matrix.translate( 0.5 * Width, 0.5 * Height );
184 matrix.rotate( m_angle );
185 painter.setWorldMatrix( matrix );
186
187 painter.setPen( m_pen );
188 painter.setBrush( m_brush );
189 painter.setFont( m_font );
190 QRectF container( -0.5 * Width, -0.5 * Height, Width, 0.5 * Height );
191 painter.drawText( container, Qt::AlignHCenter | Qt::AlignBottom,
192 m_text, &boundingRect );
193 m_referenceBottomLeft = QPointF( boundingRect.bottomLeft().x(), 0.0 );
194 textBottomRight = QPointF( boundingRect.bottomRight().x(), 0.0 );
195 m_textAscendVector = boundingRect.topRight() - textBottomRight;
196 m_textBaseLineVector = textBottomRight - m_referenceBottomLeft;
197
198 // FIXME translate topright by char height
199 boundingRect = matrix.mapRect( boundingRect );
200 m_referenceBottomLeft = matrix.map( m_referenceBottomLeft )
201 - boundingRect.topLeft();
202 textBottomRight = matrix.map( textBottomRight )
203 - boundingRect.topLeft();
204 m_textAscendVector = matrix.map( m_textAscendVector )
205 - matrix.map( Center );
206 m_textBaseLineVector = matrix.map( m_textBaseLineVector )
207 - matrix.map( Center );
208 }
209
210 m_dirty = false; // now all the calculation vectors are valid
211
212 QPixmap temp( static_cast<int>( boundingRect.width() ),
213 static_cast<int>( boundingRect.height() ) );
214 {
215 temp.fill( FullTransparent );
216 QPainter painter( &temp );
217 #ifdef Q_WS_X11
218 painter.drawImage( QPointF( 0.0, 0.0 ), pixmap, boundingRect );
219 #else
220 painter.drawPixmap( QPointF( 0.0, 0.0 ), pixmap, boundingRect );
221 #endif
222 // #define PRERENDEREDLABEL_DEBUG
223 #ifdef PRERENDEREDLABEL_DEBUG
224 painter.setPen( QPen( Qt::red, 2 ) );
225 painter.setBrush( Qt::red );
226 // paint markers for the reference points
227 QList<KChartEnums::PositionValue> positions;
228 positions << KChartEnums::PositionCenter
229 << KChartEnums::PositionNorthWest
230 << KChartEnums::PositionNorth
231 << KChartEnums::PositionNorthEast
232 << KChartEnums::PositionEast
233 << KChartEnums::PositionSouthEast
234 << KChartEnums::PositionSouth
235 << KChartEnums::PositionSouthWest
236 << KChartEnums::PositionWest;
237 Q_FOREACH( KChartEnums::PositionValue position, positions ) { //krazy:exclude=foreach
238 static const double Radius = 0.5;
239 static const double Diameter = 2 * Radius;
240
241 QPointF point ( referencePointLocation( position ) );
242 painter.drawEllipse( QRectF( point - QPointF( Radius, Radius ),
243 QSizeF( Diameter, Diameter ) ) );
244 }
245 #endif
246 }
247
248 m_pixmap = temp;
249 }
250
referencePointLocation() const251 QPointF PrerenderedLabel::referencePointLocation() const
252 {
253 return referencePointLocation( referencePoint() );
254 }
255
referencePointLocation(KChartEnums::PositionValue position) const256 QPointF PrerenderedLabel::referencePointLocation( KChartEnums::PositionValue position ) const
257 {
258 if ( m_dirty ) {
259 INC_MISS_COUNT;
260 paint();
261 } else {
262 INC_HIT_COUNT;
263 }
264
265 switch ( position ) {
266 case KChartEnums::PositionCenter:
267 return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + 0.5 * m_textAscendVector;
268 case KChartEnums::PositionNorthWest:
269 return m_referenceBottomLeft + m_textAscendVector;
270 case KChartEnums::PositionNorth:
271 return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + m_textAscendVector;
272 case KChartEnums::PositionNorthEast:
273 return m_referenceBottomLeft + m_textBaseLineVector + m_textAscendVector;
274 case KChartEnums::PositionEast:
275 return m_referenceBottomLeft + 0.5 * m_textAscendVector;
276 case KChartEnums::PositionSouthEast:
277 return m_referenceBottomLeft + m_textBaseLineVector;
278 case KChartEnums::PositionSouth:
279 return m_referenceBottomLeft + 0.5 * m_textBaseLineVector;
280 case KChartEnums::PositionSouthWest:
281 return m_referenceBottomLeft;
282 case KChartEnums::PositionWest:
283 return m_referenceBottomLeft + m_textBaseLineVector + 0.5 * m_textAscendVector;
284
285 case KChartEnums::PositionUnknown: // intentional fall-through
286 case KChartEnums::PositionFloating: // intentional fall-through
287 return QPointF();
288 }
289
290 return QPointF();
291 }
292