LoginSignup
2
2

More than 5 years have passed since last update.

Qtで文字列の画面上の幅を求める際のタブの扱い

Last updated at Posted at 2016-10-19

Qtで文字列の画面上の幅を求めるには、QFontMetricsクラスを使います。

QFontMetrics
QRect boundingRect(QChar ch) const
QRect boundingRect(const QString &text) const
QRect boundingRect(const QRect &rect, int flags, const QString &text, int tabStops = 0, int *tabArray = Q_NULLPTR) const
QRect boundingRect(int x, int y, int width, int height, int flags, const QString &text, int tabStops = 0, int *tabArray = Q_NULLPTR) const
QSize size(int flags, const QString &text, int tabStops = 0, int *tabArray = Q_NULLPTR) const
int width(const QString&, int)

以降の議論では、boundingRectのオーバーライドを番号で区別します。また、混乱の無いときはクラス名を省略します。

QRect boundingRect1(QChar ch) const
QRect boundingRect2(const QString &text) const
QRect boundingRect3(const QRect &rect, int flags, const QString &text, int tabStops = 0, int *tabArray = Q_NULLPTR) const
QRect boundingRect4(int x, int y, int width, int height, int flags, const QString &text, int tabStops = 0, int *tabArray = Q_NULLPTR) const

sizeboundingRect4boundingRect3を呼ぶので、boundingRect3と同一視します。

qtbase/src/gui/text/qfontmetrics.cpp
QSize QFontMetrics::size(int flags, const QString &text, int tabStops, int *tabArray) const
{
    return boundingRect(QRect(0,0,0,0), flags | Qt::TextLongestVariant, text, tabStops, tabArray).size();
}
qtbase/src/gui/text/qfontmetrics.h
inline QRect boundingRect(int x, int y, int w, int h, int flags, const QString &text,
                          int tabstops = 0, int *tabarray = Q_NULLPTR) const
    { return boundingRect(QRect(x, y, w, h), flags, text, tabstops, tabarray); }

boundingRect1は文字列ではなく文字を対象とするので、ここでは扱いません。

タブの問題

文字列がタブを含む場合、関数(特に、boundingRectのどのバージョンを使うか)によって求まる幅が異なります。

その仕様はQtの闇に包まれているソースコードを読まないとわかりません。

ポイントは3つあります。

  • 引数にtabStopsがあるか
  • フラグQt::TextExpandTabsの有無
  • tabStopsの符号

タブ幅を含むテキストレイアウトのオプションは、QTextOptionクラスが保持しています。

QTextOptionが保持するタブ幅のデフォルト値は-1です

qtbase/src/gui/text/qtextoption.cpp
QTextOption::QTextOption()
    : align(Qt::AlignLeft),
      wordWrap(QTextOption::WordWrap),
      design(false),
      unused(0),
      unused2(0),
      f(0),
      tab(-1),
      d(0)
{
   
}

引数にtabStopsが無い関数の場合

widthboundingRect2が該当します。

タブ幅はQTextOptionクラスで保持していて、その値はデフォルト値-1のままになります。

これらの関数は最終的にQTextEngine::calculateTabWidth関数をコールします。
その段階でタブ幅が0以下の場合、タブ幅は80pxになります。

qtbase/src/gui/text/qtextengine.cpp
QFixed QTextEngine::calculateTabWidth(int item, QFixed x) const
{
    
    QFixed tab = QFixed::fromReal(option.tabStop());
    if (tab <= 0)
        tab = 80; // default
    tab *= dpiScale;
    QFixed nextTabPos = ((x / tab).truncate() + 1) * tab;
    QFixed tabWidth = nextTabPos - x;

    return tabWidth;
}

引数にtabStopsがある関数の場合

boundingRect3tabStopsを指定できます。デフォルト引数は0です。

boundingRect3qpainter.cppで定義されるqt_format_text関数をコールします。ここで、boundingRect3の引数flagsに応じて重要な前処理が行われます。

Qt::TextExpandTabsを指定しない場合

タブは半角スペースに置き換えられます。タブが無くなるので、tabStopsの値は結果に影響を与えません。

Qt::TextExpandTabsを指定する場合

ドキュメントには以下のように書いてあります。

If Qt::TextExpandTabs is set in flags, then: if tabArray is non-null, it specifies a 0-terminated sequence of pixel-positions for tabs; otherwise if tabStops is non-zero, it is used as the tab spacing (in pixels).

しかし、実際はboundingRect3の引数tabStopsの符号で3通りに分かれます。

tabArrayは、tabStopsの値がタブの深さで変わるというだけなので、以降はtabStopsについて考えます。

tabStopsが0の場合

tabStopsを省略した場合も0になります。

このとき、タブ幅は「文字xの幅の8倍」になります (0pxにはなりません)。

qpainter.cpp
void qt_format_text(const QFont &fnt, const QRectF &_r,
                    int tf, const QTextOption *option, const QString& str, QRectF *brect,
                    int tabstops, int *ta, int tabarraylen,
                    QPainter *painter)
{
    

    // compatible behaviour to the old implementation. Replace
    // tabs by spaces
    int old_offset = offset;
    for (; offset < text.length(); offset++) {
        QChar chr = text.at(offset);
        if () {
            
        } else if (chr == QLatin1Char('\t')) {
            if (!expandtabs) {
                text[offset] = QLatin1Char(' ');
            } else if (!tabarraylen && !tabstops) {
                tabstops = qRound(fm.width(QLatin1Char('x'))*8);
            }
        } else if () {
            
        }
    }
    
}

この後、最終的にQTextEngine::calculateTabWidth関数をコールします。

(タブ幅が0のままだと80pxになってしまいますが、この場合は先にxの8倍が代入されるのでそうはなりません)

tabStopsが負の場合

最終的にQTextEngine::calculateTabWidth関数をコールし、80pxになります。

tabStopsが正の場合

ドキュメントの通り、tabStopsの値が使われます。

まとめ

デフォルト値として80pxとxの幅の8倍という2つの値があり、どちらが使われるかはドキュメントからはわかりません(そもそもデフォルト値自体が書かれていない)。

  • 引数にtabStopsなし
    80px
  • 引数にtabStopsあり
    • TextExpandTabs指定なし
      半角スペースの幅
    • TextExpandTabs指定あり
      • tabStops < 0
        80px
      • tabStops = 0 (デフォルト)
        xの幅の8倍
      • tabStops > 0
        tabStopsの値
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2