File indexing completed on 2024-05-12 15:54:19

0001 /*
0002  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
0003  *
0004  * This file is part of the KD Chart library.
0005  *
0006  * This program is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU General Public License as
0008  * published by the Free Software Foundation; either version 2 of
0009  * the License, or (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License
0017  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
0018  */
0019 
0020 #include "KChartTextLabelCache.h"
0021 
0022 #include <cmath>
0023 
0024 #include <QtDebug>
0025 #include <QImage>
0026 #include <QPixmap>
0027 #include <QPainter>
0028 #include <QApplication>
0029 
0030 #ifndef NDEBUG
0031 int HitCount = 0;
0032 int MissCount = 0;
0033 #define INC_HIT_COUNT { ++HitCount; }
0034 #define INC_MISS_COUNT  { ++MissCount; }
0035 #define DUMP_CACHE_STATS \
0036     if ( HitCount != 0 && MissCount != 0 ) { \
0037         int total = HitCount + MissCount; \
0038         qreal hitQuote = ( 1.0 * HitCount ) / total; \
0039         qDebug() << "PrerenderedLabel dtor: hits/misses/total:" \
0040         << HitCount << "/" << MissCount << "/" << total \
0041                  << "(" << 100 * hitQuote << "% hits)"; \
0042     }
0043 #else
0044 #define INC_HIT_COUNT
0045 #define INC_MISS_COUNT
0046 #define DUMP_CACHE_STATS
0047 #endif
0048 
0049 PrerenderedElement::PrerenderedElement()
0050     : m_referencePoint( KChartEnums::PositionNorthWest )
0051 {
0052 }
0053 
0054 void PrerenderedElement::setPosition( const QPointF& position )
0055 {   // this does not invalidate the element
0056     m_position = position;
0057 }
0058 
0059 const QPointF& PrerenderedElement::position() const
0060 {
0061     return m_position;
0062 }
0063 
0064 void PrerenderedElement::setReferencePoint( KChartEnums::PositionValue point )
0065 {   // this does not invalidate the element
0066     m_referencePoint = point;
0067 }
0068 
0069 KChartEnums::PositionValue PrerenderedElement::referencePoint() const
0070 {
0071     return m_referencePoint;
0072 }
0073 
0074 PrerenderedLabel::PrerenderedLabel()
0075     : PrerenderedElement()
0076     , m_dirty( true )
0077     , m_font( qApp->font() )
0078     , m_brush( Qt::black )
0079     , m_pen( Qt::black ) // do not use anything invisible
0080     , m_angle( 0.0 )
0081 {
0082 }
0083 
0084 PrerenderedLabel::~PrerenderedLabel()
0085 {
0086     DUMP_CACHE_STATS;
0087 }
0088 
0089 void PrerenderedLabel::invalidate() const
0090 {
0091     m_dirty = true;
0092 }
0093 
0094 void PrerenderedLabel::setFont( const QFont& font )
0095 {
0096     m_font = font;
0097     invalidate();
0098 }
0099 
0100 const QFont& PrerenderedLabel::font() const
0101 {
0102     return m_font;
0103 }
0104 
0105 void PrerenderedLabel::setText( const QString& text )
0106 {
0107     m_text = text;
0108     invalidate();
0109 }
0110 
0111 const QString& PrerenderedLabel::text() const
0112 {
0113     return m_text;
0114 }
0115 
0116 void PrerenderedLabel::setBrush( const QBrush& brush )
0117 {
0118     m_brush = brush;
0119     invalidate();
0120 }
0121 
0122 const QBrush& PrerenderedLabel::brush() const
0123 {
0124     return m_brush;
0125 }
0126 
0127 void PrerenderedLabel::setAngle( qreal angle )
0128 {
0129     m_angle = angle;
0130     invalidate();
0131 }
0132 
0133 qreal PrerenderedLabel::angle() const
0134 {
0135     return m_angle;
0136 }
0137 
0138 const QPixmap& PrerenderedLabel::pixmap() const
0139 {
0140     if ( m_dirty ) {
0141         INC_MISS_COUNT;
0142         paint();
0143     } else {
0144         INC_HIT_COUNT;
0145     }
0146     return m_pixmap;
0147 }
0148 
0149 void PrerenderedLabel::paint() const
0150 {
0151     // FIXME find a better value using font metrics of text (this
0152     // requires finding the diameter of the circle formed by rotating
0153     // the bounding rect around the center):
0154     const int Width = 1000;
0155     const int Height = Width;
0156 
0157     QRectF boundingRect;
0158     const QColor FullTransparent( 255, 255, 255, 0 );
0159 #ifdef Q_WS_X11
0160     QImage pixmap( Width, Height, QImage::Format_ARGB32_Premultiplied );
0161     qWarning() << "PrerenderedLabel::paint: using QImage for prerendered labels "
0162                << "to work around XRender/Qt4 bug.";
0163 #else
0164     QPixmap pixmap( Width, Height );
0165 #endif
0166     // pixmap.fill( FullTransparent );
0167     {
0168         static const QPointF Center ( 0.0, 0.0 );
0169         QPointF textBottomRight;
0170         QPainter painter( &pixmap );
0171         painter.setRenderHint(QPainter::TextAntialiasing, true );
0172         painter.setRenderHint(QPainter::Antialiasing, true );
0173 
0174         // QImage (X11 workaround) does not have fill():
0175         painter.setPen( FullTransparent );
0176         painter.setBrush( FullTransparent );
0177         QPainter::CompositionMode mode = painter.compositionMode();
0178         painter.setCompositionMode( QPainter::CompositionMode_Clear );
0179         painter.drawRect( 0, 0, Width, Height );
0180         painter.setCompositionMode( mode );
0181 
0182         QMatrix matrix;
0183         matrix.translate( 0.5 * Width,  0.5 * Height );
0184         matrix.rotate( m_angle );
0185         painter.setWorldMatrix( matrix );
0186 
0187         painter.setPen( m_pen );
0188         painter.setBrush( m_brush );
0189         painter.setFont( m_font );
0190         QRectF container( -0.5 * Width, -0.5 * Height, Width, 0.5 * Height );
0191         painter.drawText( container, Qt::AlignHCenter | Qt::AlignBottom,
0192                           m_text, &boundingRect );
0193         m_referenceBottomLeft = QPointF( boundingRect.bottomLeft().x(), 0.0 );
0194         textBottomRight = QPointF( boundingRect.bottomRight().x(), 0.0 );
0195         m_textAscendVector = boundingRect.topRight() - textBottomRight;
0196         m_textBaseLineVector = textBottomRight - m_referenceBottomLeft;
0197 
0198         // FIXME translate topright by char height
0199         boundingRect = matrix.mapRect( boundingRect );
0200         m_referenceBottomLeft = matrix.map( m_referenceBottomLeft )
0201                                 - boundingRect.topLeft();
0202         textBottomRight = matrix.map( textBottomRight )
0203                           - boundingRect.topLeft();
0204         m_textAscendVector = matrix.map( m_textAscendVector )
0205                              - matrix.map( Center );
0206         m_textBaseLineVector = matrix.map( m_textBaseLineVector )
0207                             - matrix.map( Center );
0208     }
0209 
0210     m_dirty = false; // now all the calculation vectors are valid
0211 
0212     QPixmap temp( static_cast<int>( boundingRect.width() ),
0213                   static_cast<int>( boundingRect.height() ) );
0214     {
0215         temp.fill( FullTransparent );
0216         QPainter painter( &temp );
0217 #ifdef Q_WS_X11
0218         painter.drawImage( QPointF( 0.0, 0.0 ), pixmap, boundingRect );
0219 #else
0220         painter.drawPixmap( QPointF( 0.0, 0.0 ), pixmap, boundingRect );
0221 #endif
0222 // #define PRERENDEREDLABEL_DEBUG
0223 #ifdef PRERENDEREDLABEL_DEBUG
0224         painter.setPen( QPen( Qt::red, 2 ) );
0225         painter.setBrush( Qt::red );
0226         // paint markers for the reference points
0227         QList<KChartEnums::PositionValue> positions;
0228         positions << KChartEnums::PositionCenter
0229                   << KChartEnums::PositionNorthWest
0230                   << KChartEnums::PositionNorth
0231                   << KChartEnums::PositionNorthEast
0232                   << KChartEnums::PositionEast
0233                   << KChartEnums::PositionSouthEast
0234                   << KChartEnums::PositionSouth
0235                   << KChartEnums::PositionSouthWest
0236                   << KChartEnums::PositionWest;
0237         Q_FOREACH( KChartEnums::PositionValue position, positions ) { //krazy:exclude=foreach
0238             static const double Radius = 0.5;
0239             static const double Diameter = 2 * Radius;
0240 
0241             QPointF point ( referencePointLocation( position ) );
0242             painter.drawEllipse( QRectF( point - QPointF( Radius, Radius ),
0243                                          QSizeF( Diameter, Diameter ) ) );
0244         }
0245 #endif
0246     }
0247 
0248     m_pixmap = temp;
0249 }
0250 
0251 QPointF PrerenderedLabel::referencePointLocation() const
0252 {
0253     return referencePointLocation( referencePoint() );
0254 }
0255 
0256 QPointF PrerenderedLabel::referencePointLocation( KChartEnums::PositionValue position ) const
0257 {
0258     if ( m_dirty ) {
0259         INC_MISS_COUNT;
0260         paint();
0261     } else {
0262         INC_HIT_COUNT;
0263     }
0264 
0265     switch ( position ) {
0266     case KChartEnums::PositionCenter:
0267         return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + 0.5 * m_textAscendVector;
0268     case KChartEnums::PositionNorthWest:
0269         return m_referenceBottomLeft + m_textAscendVector;
0270     case KChartEnums::PositionNorth:
0271         return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + m_textAscendVector;
0272     case KChartEnums::PositionNorthEast:
0273         return m_referenceBottomLeft + m_textBaseLineVector + m_textAscendVector;
0274     case KChartEnums::PositionEast:
0275         return m_referenceBottomLeft + 0.5 * m_textAscendVector;
0276     case KChartEnums::PositionSouthEast:
0277         return m_referenceBottomLeft + m_textBaseLineVector;
0278     case KChartEnums::PositionSouth:
0279         return m_referenceBottomLeft + 0.5 * m_textBaseLineVector;
0280     case KChartEnums::PositionSouthWest:
0281         return m_referenceBottomLeft;
0282     case KChartEnums::PositionWest:
0283         return m_referenceBottomLeft + m_textBaseLineVector + 0.5 * m_textAscendVector;
0284 
0285     case KChartEnums::PositionUnknown: // intentional fall-through
0286     case KChartEnums::PositionFloating: // intentional fall-through
0287         return QPointF(); 
0288     }
0289     
0290     return QPointF();
0291 }