Как выровнять QPainter drawText вокруг точки, а не прямоугольника?

Я хочу установить выравнивание текста, используя одну точку в качестве координаты, а не прямоугольник.

Насколько я понимаю, QPainter::drawText позволяет установить выравнивание текста только тогда, когда я передаю координаты в виде прямоугольника.

Как установить выравнивание текста, если я хочу выровнять текст относительно точки, а не прямоугольника?


person Ufx    schedule 18.07.2014    source источник


Ответы (2)


Когда вы передаете начальную точку для рисования текста, вы фактически рисуете текст на большом прямоугольнике, нижний левый угол которого находится в данной точке. Итак, все, что вам нужно, это предложить подходящий «бесконечный» прямоугольник на основе вашей начальной точки и выбранного выравнивания:

скриншот

// 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();
}
person Kuba hasn't forgotten Monica    schedule 18.07.2014
comment
Смещение шрифта ascent*0.25 или font descent*1 для получения того же базового позиционирования, что и у drawText(x, y, text); - person Eejin; 07.01.2018
comment
Есть ли причина использовать магическое число 32767.0? Похоже на максимальное значение для 16-битного целого числа со знаком, но qreal — это double, верно? Это просто какое-то произвольное большое число (ваше бесконечное)? - person djvg; 04.11.2019
comment
Координаты в Qt могут быть ограничены 16-битным диапазоном со знаком из-за его наследия. Вы можете использовать std::numeric_limits<qreal>.max() и т. д., но сначала необходимо проверить его на каждой платформе и версии Qt. - person Kuba hasn't forgotten Monica; 06.11.2019

Я изменил существующий ответ (https://stackoverflow.com/a/24831796), чтобы сделать его более самодокументированным и добавить поддержку для «базового выравнивания», если вы не укажете вертикальное выравнивание.

  • Переменные, начинающиеся с d, являются дельта-векторами. Остальные переменные являются абсолютными координатами.
  • Я не уверен, почему изначально был выбран 32767.0, и я не проверял, меняет ли скорость изменение значения.
  • Я кэширую QFontMetrics.descent(), потому что не знаю, медленно это или нет.

(К сожалению, если я попытаюсь добавить код после маркированного списка, форматирование испортится.)

#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();
}
person nyanpasu64    schedule 08.07.2019