1. ホーム
  2. android

[解決済み】Android Paint: .measureText() vs .getTextBounds()

2022-05-11 14:31:01

質問

を使用してテキストを測定しています。 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

解決方法は?

このような問題を検査するために、私が行ったことを実行することができます。

Androidのソースコード、Paint.javaのソースを見て、measureTextとgetTextBoundsの両方のメソッドを見てください。 measureTextはnative_measureTextを、getTextBoundsはnativeGetStringBoundsを呼び出し、これらはC++で実装されたネイティブメソッドであることが分かると思います。

そこで、両方を実装しているPaint.cppの勉強を続けることになるわけです。

native_measureText -> SkPaintGlue::measureText_CII

nativeGetStringBounds -> SkPaintGlue::getStringBounds

では、これらのメソッドがどこが違うのかを調べてみましょう。 いくつかのパラメータをチェックした結果、どちらもSkia Lib(Androidの一部)の関数SkPaint::measureTextを呼び出しますが、異なるオーバーロードフォームを呼び出しています。

さらにSkiaを掘り下げてみると、どちらも同じ関数で同じ計算を行い、異なる結果を返していることがわかります。

質問にお答えします。 どちらの呼び出しも同じ計算をします。結果が異なる可能性があるのは getTextBounds は境界を整数値で返すのに対し メジャテキスト はfloat値を返す。

Paint.cpp の SkPaintGlue::doTextBounds で関数 SkRect::roundOut を呼び出すと、float から int への変換時に丸め誤差が発生します。

この2つの呼び出しの計算された幅の差は最大で1かもしれません。

編集 2011年10月4日

可視化よりいいかもしれないこと。自分自身の探求と、報奨金目当てでやってみました(笑)

これはフォントサイズ60で、赤色は 境界 の矩形、紫色はmeasureTextの結果です。

バウンズ左は左から何ピクセルか離れたところから始まっており、メジャーテキストの値は左右ともにこの値だけ増加することがわかります。これは、グリフの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);
  }