web-dev-qa-db-fra.com

Android Paint: .measureText () vs .getTextBounds ()

Je mesure le texte avec Paint.getTextBounds(), car je souhaite obtenir à la fois la hauteur et la largeur du texte à restituer. Cependant, le texte réel rendu est toujours un peu plus large que le .width() de l'information Rect renseignée par getTextBounds().

À ma grande surprise, j'ai testé .measureText() et constaté qu'il renvoyait une valeur différente (supérieure). Je l'ai essayé, et l'ai trouvé correct.

Pourquoi signalent-ils des largeurs différentes? Comment puis-je obtenir correctement la hauteur et la largeur? Je veux dire, je peut utiliser .measureText(), mais je ne saurais pas si je devrais faire confiance à la .height() renvoyée par getTextBounds().

Comme demandé, voici le code minimal pour reproduire le problème:

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())
    );
}

La sortie montre que la différence non seulement dépasse 1 (et qu'il ne s'agit pas d'une erreur d'arrondi de dernière minute), mais semble également augmenter avec la taille (j'étais sur le point de tirer davantage de conclusions, mais elle peut dépendre entièrement de la police de caractère):

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
186
slezica

Vous pouvez faire ce que j'ai fait pour inspecter ce problème:

Étudiez le code source Android, source Paint.Java, voir les deux méthodes measureText et getTextBounds. Vous apprendrez que measureText appelle native_measureText et getTextBounds appelle nativeGetStringBounds, qui sont des méthodes natives implémentées en C++.

Vous continuerez donc à étudier Paint.cpp, qui implémente les deux.

native_measureText -> SkPaintGlue :: measureText_CII

nativeGetStringBounds -> SkPaintGlue :: getStringBounds

Maintenant, votre étude vérifie où ces méthodes diffèrent. Après quelques vérifications de paramètres, les deux appellent la fonction SkPaint :: measureText dans Skia Lib (qui fait partie d'Android), mais appellent toutes deux une forme surchargée différente.

En creusant plus loin dans Skia, je vois que les deux appels résultent dans le même calcul dans la même fonction, ne retournent le résultat que différemment.

Pour répondre à votre question: Vos deux appels font le même calcul. La différence de résultat possible réside en fait dans le fait que getTextBounds renvoie les bornes sous forme d'entier, tandis que measureText retourne une valeur flottante.

Donc, vous obtenez une erreur d'arrondi lors de la conversion de float en int, et cela se produit dans Paint.cpp dans SkPaintGlue :: doTextBounds dans l'appel de la fonction SkRect :: roundOut.

La différence entre la largeur calculée de ces deux appels peut être au maximum 1.

EDIT 4 Oct 2011

Quoi de mieux que la visualisation. J'ai pris l'effort, pour sa propre exploration, et pour mériter une prime :)

enter image description here

Ceci est la taille de la police 60, en rouge est le rectangle de borne , en violet est le résultat de measureText.

On voit que la partie gauche des limites commence par quelques pixels à partir de la gauche et que la valeur de measureText est incrémentée de cette valeur à gauche et à droite. C'est ce qu'on appelle la valeur AdvanceX de Glyph. (J'ai découvert cela dans les sources de Skia dans SkPaint.cpp)

Ainsi, le résultat du test est que measureText ajoute une valeur avancée au texte des deux côtés, tandis que getTextBounds calcule des limites minimales là où le texte convient.

J'espère que ce résultat vous sera utile.

Code de test:

  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);
  }
360
Pointer Null

D'après mon expérience, getTextBounds renverra ce rectangle minimal absolu qui encapsule le texte, pas nécessairement la largeur mesurée utilisée lors du rendu. Je veux aussi dire que measureText suppose une ligne.

Pour obtenir des résultats de mesure précis, utilisez StaticLayout pour afficher le texte et extraire les mesures.

Par exemple:

String text = "text";
TextPaint textPaint = textView.getPaint();
int boundedWidth = 1000;

StaticLayout layout = new StaticLayout(text, textPaint, boundedWidth , Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
int height = layout.getHeight();
21
Chase

La réponse des souris est excellente ... Et voici la description du problème réel:

La réponse simple et rapide est que Paint.getTextBounds(String text, int start, int end, Rect bounds) renvoie Rect qui ne commence pas à (0,0). Autrement dit, pour obtenir la largeur réelle du texte qui sera définie en appelant Canvas.drawText(String text, float x, float y, Paint paint) avec l'objet identique Paint de getTextBounds (), vous devez ajouter la position gauche de Rect. Quelque chose comme ca:

public int getTextWidth(String text, Paint paint) {
    Rect bounds = new Rect();
    Paint.getTextBounds(text, 0, end, bounds);
    int width = bounds.left + bounds.width();
    return width;
}

Remarquez ceci bounds.left - ceci est la clé du problème.

De cette façon, vous recevrez la même largeur de texte que celle que vous recevriez avec Canvas.drawText().

Et la même fonction devrait être utilisée pour obtenir height du texte:

public int getTextHeight(String text, Paint paint) {
    Rect bounds = new Rect();
    Paint.getTextBounds(text, 0, end, bounds);
    int height = bounds.bottom + bounds.height();
    return height;
}

P.s .: Je n'ai pas testé ce code exact, mais testé la conception.


Une explication beaucoup plus détaillée est donnée dans la réponse this .

18
Prizoff

Désolé de répondre à nouveau à cette question ... Je devais intégrer l'image.

Je pense que les résultats trouvés par @mice sont trompeurs. Les observations peuvent être correctes pour la taille de police de 60, mais elles deviennent beaucoup plus différentes lorsque le texte est plus petit. Par exemple. 10px. Dans ce cas, le texte est réellement tracé au-delà des limites.

enter image description here

Code source de la capture d'écran:

  @Override
  protected void onDraw( Canvas canvas ) {
    for( int i = 0; i < 20; i++ ) {
      int startSize = 10;
      int curSize = i + startSize;
      Paint.setTextSize( curSize );
      String text = i + startSize + " - " + TEXT_SNIPPET;
      Rect bounds = new Rect();
      Paint.getTextBounds( text, 0, text.length(), bounds );
      float top = STEP_DISTANCE * i + curSize;
      bounds.top += top;
      bounds.bottom += top;
      canvas.drawRect( bounds, bgPaint );
      canvas.drawText( text, 0, STEP_DISTANCE * i + curSize, Paint );
    }
  }
12
Moritz

CLAUSE DE NON-RESPONSABILITÉ: Cette solution n'est pas précise à 100% en termes de détermination de la largeur minimale.

Je cherchais également à mesurer le texte sur une toile. Après avoir lu le très bon billet de souris, j’ai eu quelques problèmes pour mesurer un texte multiligne. Il n’ya pas de solution évidente à partir de ces contributions mais après quelques recherches, j’ai traversé la classe StaticLayout. Il vous permet de mesurer du texte multiligne (texte avec "\ n") et de configurer beaucoup plus de propriétés de votre texte via la peinture associée.

Voici un extrait montrant comment mesurer un texte multiligne:

private StaticLayout measure( TextPaint textPaint, String text, Integer wrapWidth ) {
    int boundedWidth = Integer.MAX_VALUE;
    if (wrapWidth != null && wrapWidth > 0 ) {
       boundedWidth = wrapWidth;
    }
    StaticLayout layout = new StaticLayout( text, textPaint, boundedWidth, Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false );
    return layout;
}

Le wrapwitdh est capable de déterminer si vous voulez limiter votre texte multiligne à une certaine largeur.

Puisque StaticLayout.getWidth () ne renvoie que cette boundedWidth, vous devez effectuer un autre pas pour obtenir la largeur maximale requise par votre texte multiligne. Vous êtes en mesure de déterminer la largeur de chaque ligne et la largeur maximale correspond à la largeur de ligne la plus élevée:

private float getMaxLineWidth( StaticLayout layout ) {
    float maxLine = 0.0f;
    int lineCount = layout.getLineCount();
    for( int i = 0; i < lineCount; i++ ) {
        if( layout.getLineWidth( i ) > maxLine ) {
            maxLine = layout.getLineWidth( i );
        }
    }
    return maxLine;
}
10
Moritz

Il existe un autre moyen de mesurer les limites du texte avec précision. Vous devez d’abord obtenir le chemin de la peinture et du texte actuels. Dans votre cas, cela devrait être comme ceci:

p.getTextPath(someText, 0, someText.length(), 0.0f, 0.0f, mPath);

Après cela, vous pouvez appeler:

mPath.computeBounds(mBoundsPath, true);

Dans mon code, il retourne toujours les valeurs correctes et attendues. Mais, pas sûr si cela fonctionne plus vite que votre approche.

2
Roman

La différence entre getTextBounds et measureText est décrite avec l'image ci-dessous.

En bref,

  1. getTextBounds consiste à obtenir le RECT du texte exact. La measureText est la longueur du texte, y compris l’espace supplémentaire à gauche et à droite.

  2. S'il y a des espaces entre le texte, il est mesuré dans measureText mais n'inclut pas la longueur de TextBounds, bien que les coordonnées soient décalées.

  3. Le texte pourrait être incliné (Skew) à gauche. Dans ce cas, le côté gauche de la zone de délimitation dépasserait de la mesure du texte de mesure (mesureText) et la longueur totale du texte lié serait plus grande que measureText

  4. Le texte pourrait être incliné (Skew) à droite. Dans ce cas, le côté droit du cadre de sélection dépasserait de la mesure du texte de mesure, et la longueur totale du texte lié serait supérieure à measureText

enter image description here

0
Elye

Voici comment j'ai calculé les dimensions réelles de la première lettre (vous pouvez modifier l'en-tête de la méthode en fonction de vos besoins, c'est-à-dire au lieu de char[] utilisez String):

private void calculateTextSize(char[] text, PointF outSize) {
    // use measureText to calculate width
    float width = mPaint.measureText(text, 0, 1);

    // use height from getTextBounds()
    Rect textBounds = new Rect();
    mPaint.getTextBounds(text, 0, 1, textBounds);
    float height = textBounds.height();
    outSize.x = width;
    outSize.y = height;
}

Notez que j'utilise TextPaint au lieu de la classe Paint originale.

0
milosmns