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 }