レンダリングされるテキストの高さと幅の両方を取得したいので、を使用してテキストを測定していますPaint.getTextBounds()
。ただし、レンダリングされる実際のテキストは、によって入力された情報.width()
のよりも常に少し広くなります。Rect
getTextBounds()
驚いたことに、 をテストしたところ.measureText()
、異なる(より高い)値が返されることが分かりました。 試してみたところ、正しいことが分かりました。
なぜ幅が違うと報告されるのでしょうか? 高さと幅を正しく取得するにはどうすればいいのでしょうか? つまり、できるを使用しますが、によって返される.measureText()
を信頼すべきかどうかわかりません。.height()
getTextBounds()
ご要望に応じて、問題を再現するための最小限のコードを以下に示します。
final String someText = "Hello. I believe I'm some text!";
Paint p = new Paint();
Rect bounds = new Rect();
for (float f = 10; f < 40; f += 1f) {
p.setTextSize(f);
p.getTextBounds(someText, 0, someText.length(), bounds);
Log.d("Test", String.format(
"Size %f, measureText %f, getTextBounds %d",
f,
p.measureText(someText),
bounds.width())
);
}
出力を見ると、差が 1 を超えるだけでなく (最後の丸め誤差ではない)、サイズが大きくなるにつれて差も大きくなるようです (さらに結論を出そうと思ったのですが、完全にフォントに依存している可能性があります)。
D/Test ( 607): Size 10.000000, measureText 135.000000, getTextBounds 134
D/Test ( 607): Size 11.000000, measureText 149.000000, getTextBounds 148
D/Test ( 607): Size 12.000000, measureText 156.000000, getTextBounds 155
D/Test ( 607): Size 13.000000, measureText 171.000000, getTextBounds 169
D/Test ( 607): Size 14.000000, measureText 195.000000, getTextBounds 193
D/Test ( 607): Size 15.000000, measureText 201.000000, getTextBounds 199
D/Test ( 607): Size 16.000000, measureText 211.000000, getTextBounds 210
D/Test ( 607): Size 17.000000, measureText 225.000000, getTextBounds 223
D/Test ( 607): Size 18.000000, measureText 245.000000, getTextBounds 243
D/Test ( 607): Size 19.000000, measureText 251.000000, getTextBounds 249
D/Test ( 607): Size 20.000000, measureText 269.000000, getTextBounds 267
D/Test ( 607): Size 21.000000, measureText 275.000000, getTextBounds 272
D/Test ( 607): Size 22.000000, measureText 297.000000, getTextBounds 294
D/Test ( 607): Size 23.000000, measureText 305.000000, getTextBounds 302
D/Test ( 607): Size 24.000000, measureText 319.000000, getTextBounds 316
D/Test ( 607): Size 25.000000, measureText 330.000000, getTextBounds 326
D/Test ( 607): Size 26.000000, measureText 349.000000, getTextBounds 346
D/Test ( 607): Size 27.000000, measureText 357.000000, getTextBounds 354
D/Test ( 607): Size 28.000000, measureText 369.000000, getTextBounds 365
D/Test ( 607): Size 29.000000, measureText 396.000000, getTextBounds 392
D/Test ( 607): Size 30.000000, measureText 401.000000, getTextBounds 397
D/Test ( 607): Size 31.000000, measureText 418.000000, getTextBounds 414
D/Test ( 607): Size 32.000000, measureText 423.000000, getTextBounds 418
D/Test ( 607): Size 33.000000, measureText 446.000000, getTextBounds 441
D/Test ( 607): Size 34.000000, measureText 455.000000, getTextBounds 450
D/Test ( 607): Size 35.000000, measureText 468.000000, getTextBounds 463
D/Test ( 607): Size 36.000000, measureText 474.000000, getTextBounds 469
D/Test ( 607): Size 37.000000, measureText 500.000000, getTextBounds 495
D/Test ( 607): Size 38.000000, measureText 506.000000, getTextBounds 501
D/Test ( 607): Size 39.000000, measureText 521.000000, getTextBounds 515
ベストアンサー1
このような問題を検査するために私が行ったことと同じことを実行できます:
Android ソース コード、Paint.java ソースを調べて、measureText メソッドと getTextBounds メソッドの両方を確認します。measureText は native_measureText を呼び出し、getTextBounds は nativeGetStringBounds を呼び出すことがわかります。これらは C++ で実装されたネイティブ メソッドです。
したがって、両方を実装する Paint.cpp を引き続き学習します。
ネイティブ_measureText -> SkPaintGlue::measureText_CII
ネイティブGetStringBounds -> SkPaintGlue::getStringBounds
次に、これらのメソッドが異なる点を確認します。いくつかのパラメータ チェックの後、両方とも Skia Lib (Android の一部) の関数 SkPaint::measureText を呼び出しますが、両方とも異なるオーバーロードされた形式を呼び出します。
Skia をさらに詳しく調べてみると、両方の呼び出しは同じ関数内で同じ計算になり、結果が異なる形で返されるだけであることがわかります。
あなたの質問に答えます:どちらの呼び出しでも同じ計算が行われます。結果が異なる可能性があるのは、テキスト境界を取得する境界を整数として返します。測定テキストfloat 値を返します。
したがって、float から int への変換中に丸め誤差が発生します。これは、Paint.cpp の SkPaintGlue::doTextBounds で関数 SkRect::roundOut の呼び出し時に発生します。
これら 2 つの呼び出しで計算された幅の差は最大で 1 になります。
編集 2011年10月4日
視覚化よりも良いものは何でしょうか。私は自分自身の探求のため、そして報奨に値するために努力しました :)
これはフォントサイズ60で、赤は境界紫色の四角形は measureText の結果です。
境界の左部分は左から数ピクセルで始まり、measureText の値は左右両方でこの値だけ増加していることがわかります。これは Glyph の AdvanceX 値と呼ばれるものです。(私はこれを Skia ソースの SkPaint.cpp で発見しました)
したがって、テストの結果は、measureText が両側のテキストにいくらかの前進値を追加し、getTextBounds が指定されたテキストが収まる最小の境界を計算するというものです。
この結果があなたにとって役に立つことを願っています。
テストコード:
protected void onDraw(Canvas canvas){
final String s = "Hello. I'm some text!";
Paint p = new Paint();
Rect bounds = new Rect();
p.setTextSize(60);
p.getTextBounds(s, 0, s.length(), bounds);
float mt = p.measureText(s);
int bw = bounds.width();
Log.i("LCG", String.format(
"measureText %f, getTextBounds %d (%s)",
mt,
bw, bounds.toShortString())
);
bounds.offset(0, -bounds.top);
p.setStyle(Style.STROKE);
canvas.drawColor(0xff000080);
p.setColor(0xffff0000);
canvas.drawRect(bounds, p);
p.setColor(0xff00ff00);
canvas.drawText(s, 0, bounds.bottom, p);
}