How do I make text follow the line/curve and angle of the QPainterPath?

You can set a QTransform [doc.qt.nokia.com] on a QPainter [doc.qt.nokia.com] causing the painting to follow a transformation. The usual problem is that it’s easy to rotate using QTransform::rotate() [doc.qt.nokia.com], but since it rotates around origin which is in the top left corner, the coordinates will not match the painting done without a transformation. Using QTransform [doc.qt.nokia.com] translation, you can translate the positions back to the original position, and all of this in the name of trigonometry.

  1. #include <QtGui>
  2. #include <cmath>
  3.  
  4. class Widget : public QWidget
  5. {
  6. public:
  7.     Widget ()
  8.         : QWidget() { }
  9. private:
  10.     void paintEvent ( QPaintEvent *)
  11.     {
  12.         QString hw("hello world");
  13.         int drawWidth = width() / 100;
  14.         QPainter painter(this);
  15.         QPen pen = painter.pen();
  16.         pen.setWidth(drawWidth);
  17.         pen.setColor(Qt::darkGreen);
  18.         painter.setPen(pen);
  19.  
  20.         QPainterPath path(QPointF(0.0, 0.0));
  21.  
  22.         QPointF c1(width()*0.2,height()*0.8);
  23.         QPointF c2(width()*0.8,height()*0.2);
  24.  
  25.         path.cubicTo(c1,c2,QPointF(width(),height()));
  26.  
  27.         //draw the bezier curve
  28.         painter.drawPath(path);
  29.  
  30.         //Make the painter ready to draw chars
  31.         QFont font = painter.font();
  32.         font.setPixelSize(drawWidth*2);
  33.         painter.setFont(font);
  34.         pen.setColor(Qt::red);
  35.         painter.setPen(pen);
  36.  
  37.         qreal percentIncrease = (qreal) 1/(hw.size()+1);
  38.         qreal percent = 0;
  39.  
  40.         for ( int i = 0; i < hw.size(); i++ ) {
  41.             percent += percentIncrease;
  42.  
  43.             QPointF point = path.pointAtPercent(percent);
  44.             qreal angle = path.angleAtPercent(percent);
  45.  
  46.             qreal rad =qreal(0.017453292519943295769)*angle; // PI/180
  47.  
  48.             // From the documentation:
  49.             /**
  50.               QTransform transforms a point in the plane to another point using the following formulas:
  51.               x' = m11*x + m21*y + dx
  52.               y' = m22*y + m12*x + dy
  53.             **/
  54.             // So the idea is to find the "new position of the character
  55.             // After we apply the world rotation.
  56.             // Then translate the painter back to the original position.
  57.             qreal sina = std::sin(rad);
  58.             qreal cosa = std::cos(rad);
  59.  
  60.             // Finding the delta for the penwidth
  61.             // Don't divide by 2 because some space would be nice
  62.             qreal deltaPenX = cosa * pen.width();
  63.             qreal deltaPenY = sina * pen.width();
  64.             // Finding new posision after rotation
  65.             qreal newX = (cosa * point.x()) - (sina * point.y());
  66.             qreal newY = (cosa * point.y()) + (sina * point.x());
  67.  
  68.             // Getting the delta distance
  69.             qreal deltaX = newX - point.x();
  70.             qreal deltaY = newY - point.y();
  71.             // Applying the rotation with the translation.
  72.             QTransform tran(cosa,sina,-sina,cosa,-deltaX + deltaPenX,-deltaY - deltaPenY);
  73.  
  74.             painter.setWorldTransform(tran);
  75.             painter.drawText(point,QString(hw[i]));
  76.         }
  77.     }
  78.  
  79. };
  80.  
  81. int main(int argc, char **argv)
  82. {
  83.     QApplication app(argc, argv);
  84.     Widget widget;
  85.     widget.show();
  86.     return app.exec();
  87. }
  88.  

4 comments

June 12, 2012

Picture of ChrisW67 ChrisW67

Robot Herder

A slightly less mathematical approach gives Qt more of the work. Replace the paintEvent() for loop with:

  1.         for ( int i = 0; i < hw.size(); i++ ) {
  2.             percent += percentIncrease;
  3.  
  4.             QPointF point = path.pointAtPercent(percent);
  5.             qreal angle = path.angleAtPercent(percent);   // Clockwise is negative
  6.  
  7.             painter.save();
  8.             // Move the virtual origin to the point on the curve
  9.             painter.translate(point);
  10.             // Rotate to match the angle of the curve
  11.             // Clockwise is positive so we negate the angle from above
  12.             painter.rotate(-angle);
  13.             // Draw a line width above the origin to move the text above the line
  14.             // and let Qt do the transformations
  15.             painter.drawText(QPoint(0, -pen.width()),QString(hw[i]));
  16.             painter.restore();
  17.         }

August 3, 2012

Picture of krisztiantobias krisztiantobias

Lab Rat

And it’s a normal text without spaces

  1. QPointF point = path.pointAtPercent(path.percentAtLength(length));
  2. qreal angle = path.angleAtPercent(path.percentAtLength(length));

August 3, 2012

Picture of krisztiantobias krisztiantobias

Lab Rat

length is the old percent of course

August 3, 2012

Picture of krisztiantobias krisztiantobias

Lab Rat

so:

  1. qreal lengthIncrease = hw.size();
  2.         qreal length = 0;
  3.  
  4.         for ( int i = 0; i < hw.size(); i++ ) {
  5.                     length += lengthIncrease;
  6.  
  7.                     QPointF point = path.pointAtPercent(path.percentAtLength(length));
  8.                     qreal angle = path.angleAtPercent(path.percentAtLength(length));   // Clockwise is negative
  9.  
  10.                     painter.save();
  11.                     // Move the virtual origin to the point on the curve
  12.                     painter.translate(point);
  13.                     // Rotate to match the angle of the curve
  14.                     // Clockwise is positive so we negate the angle from above
  15.                     painter.rotate(-angle);
  16.                     // Draw a line width above the origin to move the text above the line
  17.                     // and let Qt do the transformations
  18.                     painter.drawText(QPoint(0, -pen.width()),QString(hw[i]));
  19.                     painter.restore();
  20.                 }

Write a comment

Sorry, you must be logged in to post a comment.