Skip to content

Commit 2657d88

Browse files
committed
[REFINE] enhance text rendering for rotated labels with hybrid approach
1 parent 5cafca9 commit 2657d88

File tree

2 files changed

+114
-4
lines changed

2 files changed

+114
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Version 0.14.7
44

55
- Fixed label positioning for rotated scale labels - labels are now properly positioned to account for their rotated bounding box, preventing overlap with scale backbone and ticks
6+
- Improved text rendering quality for rotated scale labels by enabling enhanced antialiasing and smooth rendering hints
7+
- Implemented hybrid rendering approach for rotated text: uses crisp direct rendering for 90-degree multiples (0°, 90°, 180°, 270°) and pixmap-based rendering for arbitrary angles to balance text quality and character alignment
68

79
## Version 0.14.6
810

qwt/scale_draw.py

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@
2828
QPointF,
2929
QRect,
3030
QRectF,
31+
QSize,
3132
Qt,
3233
qFuzzyCompare,
3334
)
34-
from qtpy.QtGui import QFont, QFontMetrics, QPalette, QTransform
35+
from qtpy.QtGui import QFont, QFontMetrics, QPainter, QPalette, QPixmap, QTransform
3536

3637
from qwt._math import qwtRadians
3738
from qwt.scale_div import QwtScaleDiv
@@ -1041,10 +1042,117 @@ def drawLabel(self, painter, value):
10411042
if lbl is None or lbl.isEmpty():
10421043
return
10431044
pos = self._labelPositionWithFont(painter.font(), value)
1044-
transform = self.labelTransformation(pos, labelSize)
1045+
1046+
# For rotated text, choose rendering method based on rotation angle
1047+
rotation = self.labelRotation()
1048+
if abs(rotation) > 1e-6:
1049+
# Check if rotation is a multiple of 90 degrees (within tolerance)
1050+
normalized_rotation = rotation % 360
1051+
is_90_degree_multiple = (
1052+
abs(normalized_rotation) < 1e-6 or
1053+
abs(normalized_rotation - 90) < 1e-6 or
1054+
abs(normalized_rotation - 180) < 1e-6 or
1055+
abs(normalized_rotation - 270) < 1e-6 or
1056+
abs(normalized_rotation - 360) < 1e-6
1057+
)
1058+
1059+
if is_90_degree_multiple:
1060+
# Use direct rendering for 90-degree multiples (crisp)
1061+
transform = self.labelTransformation(pos, labelSize)
1062+
painter.save()
1063+
painter.setRenderHint(QPainter.TextAntialiasing, True)
1064+
painter.setWorldTransform(transform, True)
1065+
lbl.draw(painter, QRect(QPoint(0, 0), labelSize.toSize()))
1066+
painter.restore()
1067+
else:
1068+
# Use pixmap-based rendering for arbitrary angles (aligned but slightly blurry)
1069+
self._drawRotatedTextWithAlignment(painter, lbl, pos, labelSize, rotation)
1070+
else:
1071+
# Use standard approach for non-rotated text
1072+
transform = self.labelTransformation(pos, labelSize)
1073+
painter.save()
1074+
painter.setRenderHint(QPainter.TextAntialiasing, True)
1075+
painter.setWorldTransform(transform, True)
1076+
lbl.draw(painter, QRect(QPoint(0, 0), labelSize.toSize()))
1077+
painter.restore()
1078+
1079+
def _drawRotatedTextWithAlignment(self, painter, lbl, pos, labelSize, rotation):
1080+
"""
1081+
Draw rotated text with improved character alignment by rendering to an
1082+
intermediate pixmap and then rotating the pixmap instead of applying
1083+
transformation to text.
1084+
:param QPainter painter: Painter
1085+
:param QwtText lbl: Label text object
1086+
:param QPointF pos: Position where to paint the label
1087+
:param QSizeF labelSize: Size of the label
1088+
:param float rotation: Rotation angle in degrees
1089+
"""
1090+
# Create a pixmap to render the text without rotation first
1091+
text_size = labelSize.toSize()
1092+
if text_size.width() <= 0 or text_size.height() <= 0:
1093+
return
1094+
1095+
# Add some padding to prevent edge clipping
1096+
padding = 2
1097+
pixmap_size = text_size + QSize(padding * 2, padding * 2)
1098+
pixmap = QPixmap(pixmap_size)
1099+
pixmap.fill(Qt.transparent)
1100+
1101+
# Render the text to the pixmap without any rotation
1102+
pixmap_painter = QPainter(pixmap)
1103+
pixmap_painter.setRenderHint(QPainter.TextAntialiasing, True)
1104+
1105+
# Set font and color from QwtText
1106+
if lbl.testPaintAttribute(QwtText.PaintUsingTextFont):
1107+
pixmap_painter.setFont(lbl.font())
1108+
else:
1109+
pixmap_painter.setFont(painter.font())
1110+
1111+
if (
1112+
lbl.testPaintAttribute(QwtText.PaintUsingTextColor)
1113+
and lbl.color().isValid()
1114+
):
1115+
pixmap_painter.setPen(lbl.color())
1116+
else:
1117+
pixmap_painter.setPen(painter.pen())
1118+
1119+
# Draw text on pixmap without rotation for perfect character alignment
1120+
text_rect = QRect(padding, padding, text_size.width(), text_size.height())
1121+
lbl.draw(pixmap_painter, text_rect)
1122+
pixmap_painter.end()
1123+
1124+
# Now draw the pixmap with rotation
10451125
painter.save()
1046-
painter.setWorldTransform(transform, True)
1047-
lbl.draw(painter, QRect(QPoint(0, 0), labelSize.toSize()))
1126+
1127+
# Get alignment flags for positioning
1128+
flags = self.labelAlignment()
1129+
if flags == 0:
1130+
flags = self.Flags[self.alignment()]
1131+
1132+
# Calculate alignment offsets
1133+
if flags & Qt.AlignLeft:
1134+
x_offset = -labelSize.width()
1135+
elif flags & Qt.AlignRight:
1136+
x_offset = 0.0
1137+
else:
1138+
x_offset = -(0.5 * labelSize.width())
1139+
1140+
if flags & Qt.AlignTop:
1141+
y_offset = -labelSize.height()
1142+
elif flags & Qt.AlignBottom:
1143+
y_offset = 0
1144+
else:
1145+
y_offset = -(0.5 * labelSize.height())
1146+
1147+
# Apply transformation and draw the pre-rendered pixmap
1148+
painter.translate(pos.x(), pos.y())
1149+
painter.rotate(rotation)
1150+
painter.translate(x_offset - padding, y_offset - padding)
1151+
1152+
# Use smooth pixmap transform for better quality
1153+
painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
1154+
painter.drawPixmap(0, 0, pixmap)
1155+
10481156
painter.restore()
10491157

10501158
def boundingLabelRect(self, font, value):

0 commit comments

Comments
 (0)