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