How to align QPainter drawText around a point, not a rectangle?

后端 未结 2 675
再見小時候
再見小時候 2020-12-09 17:43

I want to set text drawing alignment using one point as coordinate, not a rectangle.

As far as I understand QPainter::drawText allows to set text alignm

相关标签:
2条回答
  • 2020-12-09 18:25

    When you pass a starting point for drawing text, you're effectively drawing text on an a large rectangle that has the bottom-left corner at the given point. So all you need is to offer a suitable "infinite" rectangle based on your starting point and the chosen alignment:

    screenshot

    // https://github.com/KubaO/stackoverflown/tree/master/questions/alignments-24831484
    #include <QtGui>
    #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
    #include <QtWidgets>
    #endif
    
    void drawText(QPainter & painter, qreal x, qreal y, Qt::Alignment flags,
                  const QString & text, QRectF * boundingRect = 0)
    {
       const qreal size = 32767.0;
       QPointF corner(x, y - size);
       if (flags & Qt::AlignHCenter) corner.rx() -= size/2.0;
       else if (flags & Qt::AlignRight) corner.rx() -= size;
       if (flags & Qt::AlignVCenter) corner.ry() += size/2.0;
       else if (flags & Qt::AlignTop) corner.ry() += size;
       else flags |= Qt::AlignBottom;
       QRectF rect{corner.x(), corner.y(), size, size};
       painter.drawText(rect, flags, text, boundingRect);
    }
    
    void drawText(QPainter & painter, const QPointF & point, Qt::Alignment flags,
                  const QString & text, QRectF * boundingRect = {})
    {
       drawText(painter, point.x(), point.y(), flags, text, boundingRect);
    }
    
    int main(int argc, char *argv[])
    {
       QApplication a{argc, argv};
       QLabel label;
       QPicture pic;
       pic.setBoundingRect({-100, -100, 200, 200});
       QPainter p(&pic);
       const QPointF pt;
    
       p.drawEllipse(pt, 3, 3);
       p.setFont({"Helvetica", 40});
       p.setPen({128, 0, 0, 128});
       drawText(p, pt, Qt::AlignBottom, "_LB");
       drawText(p, pt, Qt::AlignVCenter, "_LC");
       drawText(p, pt, Qt::AlignTop, "_LT");
       p.setPen({0, 128, 0, 128});
       drawText(p, pt, Qt::AlignBottom | Qt::AlignHCenter, "MB");
       drawText(p, pt, Qt::AlignVCenter | Qt::AlignHCenter, "MC");
       drawText(p, pt, Qt::AlignTop | Qt::AlignHCenter, "MT");
       p.setPen({0, 0, 128, 128});
       drawText(p, pt, Qt::AlignBottom | Qt::AlignRight, "RB_");
       drawText(p, pt, Qt::AlignVCenter | Qt::AlignRight, "RC_");
       drawText(p, pt, Qt::AlignTop | Qt::AlignRight, "RT_");
       p.end();
    
       label.setPicture(pic);
       label.show();
       return a.exec();
    }
    
    0 讨论(0)
  • 2020-12-09 18:34

    I modified the existing answer (https://stackoverflow.com/a/24831796) to make it more self-documenting and add support for "baseline alignment" if you don't specify vertical alignment.

    • Variables starting with d are delta vectors. Other variables are absolute coordinates.
    • I'm not sure why 32767.0 was chosen originally, and I didn't test if changing the value changes speed.
    • I cache QFontMetrics.descent() because I don't know if it's slow or not.

    (Unfortunately if I try adding code after a bulleted list, the formatting gets screwed.)

    #include <QtGui>
    #if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
    #include <QtWidgets>
    #endif
    
    class BaselinePainter: public QPainter {
    private:
        int calcDescent() const {
            QFontMetrics fontMetrics(this->font());
            return fontMetrics.descent();
        }
    
        int descent = calcDescent();
    
    public:
        using QPainter::QPainter;
    
        void setFont(const QFont &f) {
            QPainter::setFont(f);
            this->descent = calcDescent();
        }
    
        void drawTextAligned(qreal x, qreal y, Qt::Alignment align,
                      const QString & text, QRectF * boundingRect = 0)
        {
           const qreal dDown = 32767.0;
           const qreal dRight = dDown;
    
           qreal left = x;
           qreal top = y;
    
           if (align & Qt::AlignHCenter) {
               left -= dRight/2.0;
           }
           else if (align & Qt::AlignRight) {
               left -= dRight;
           }
    
           if (align & Qt::AlignTop) {
               // do nothing
           }
           else if (align & Qt::AlignVCenter) {
               top -= dDown/2.0;
           }
           else if (align & Qt::AlignBottom) {
               top -= dDown;
           }
           else {
               // Emulate baseline alignment (AKA calling drawText() with a point).
    
               // https://code.woboq.org/qt5/qtbase/src/gui/painting/qpainter.cpp.html
               // Qt drawText(rect) has a simple "no-shaping" mode (undocumented Qt::TextBypassShaping, will be removed in Qt 6)
               // and a complex "glyph-script-shaping" mode.
               // My code will only be using drawText() for ASCII characters.
    
               // Each codepath computes font descent differently.
               // The simple mode probably constructs one QFontEngine per call, to compute descent.
               // The complex mode does weird things.
    
               int dDescentDown = this->descent;
    
               align |= Qt::AlignBottom;
               top -= dDown;
               top += dDescentDown;
           }
    
           QRectF rect{left, top, dRight, dDown};
           this->drawText(rect, align, text, boundingRect);
        }
    
        void drawTextAligned(const QPointF & point, Qt::Alignment align,
                      const QString & text, QRectF * boundingRect = {})
        {
           drawTextAligned(point.x(), point.y(), align, text, boundingRect);
        }
    
    };
    
    int main(int argc, char *argv[])
    {
       QApplication a{argc, argv};
       QLabel label;
       QPicture pic;
       pic.setBoundingRect({-100, -100, 200, 200});
       BaselinePainter p(&pic);
       const QPointF pt;
    
       p.drawEllipse(pt, 3, 3);
       p.setFont({"Helvetica", 16});
       p.setPen({128, 0, 0, 128});
       p.drawTextAligned(pt, Qt::AlignBottom, "aagg");
       p.drawTextAligned(pt, 0, "ga");
       p.drawTextAligned(pt, Qt::AlignVCenter, "·");
       p.drawTextAligned(pt, Qt::AlignTop, "↑");
       p.setPen({0, 128, 0, 128});
       p.drawTextAligned(pt, Qt::AlignBottom | Qt::AlignHCenter, "aagg");
       p.drawTextAligned(pt,                   Qt::AlignHCenter, "ga");
       p.drawTextAligned(pt, Qt::AlignVCenter | Qt::AlignHCenter, "·");
       p.drawTextAligned(pt, Qt::AlignTop | Qt::AlignHCenter, "↑");
       p.setPen({0, 0, 128, 128});
       p.drawTextAligned(pt, Qt::AlignBottom | Qt::AlignRight, "↘");
       p.drawTextAligned(pt, Qt::AlignVCenter | Qt::AlignRight, "→");
       p.drawTextAligned(pt, Qt::AlignTop | Qt::AlignRight, "↗");
       p.end();
    
       label.setPicture(pic);
       label.show();
       return a.exec();
    }
    
    0 讨论(0)
提交回复
热议问题