Qtで文字列の画面上の幅を求めるには、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
size
とboundingRect4
はboundingRect3
を呼ぶので、boundingRect3
と同一視します。
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();
}
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です。
QTextOption::QTextOption()
: align(Qt::AlignLeft),
wordWrap(QTextOption::WordWrap),
design(false),
unused(0),
unused2(0),
f(0),
tab(-1),
d(0)
{
⋮
}
引数にtabStopsが無い関数の場合
width
、boundingRect2
が該当します。
タブ幅はQTextOptionクラスで保持していて、その値はデフォルト値-1のままになります。
これらの関数は最終的にQTextEngine::calculateTabWidth
関数をコールします。
その段階でタブ幅が0以下の場合、タブ幅は80pxになります。
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がある関数の場合
boundingRect3
はtabStops
を指定できます。デフォルト引数は0です。
boundingRect3
はqpainter.cppで定義されるqt_format_text
関数をコールします。ここで、boundingRect3
の引数flags
に応じて重要な前処理が行われます。
Qt::TextExpandTabsを指定しない場合
タブは半角スペースに置き換えられます。タブが無くなるので、tabStops
の値は結果に影響を与えません。
Qt::TextExpandTabsを指定する場合
ドキュメントには以下のように書いてあります。
If
Qt::TextExpandTabs
is set inflags
, then: iftabArray
is non-null, it specifies a 0-terminated sequence of pixel-positions for tabs; otherwise iftabStops
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にはなりません)。
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の値
- tabStops < 0
- TextExpandTabs指定なし