File indexing completed on 2024-05-12 04:20:37

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