Programming

Android Paint measureText vs getTextBounds

27 September 2026 · 9 min read

Android Paint measureText vs getTextBounds

Developing custom views on Android often requires precise control over text rendering. A common challenge developers face is accurately measuring text dimensions to position elements, calculate layouts, or ensure text fits within a given area. The Android Paint class provides two primary methods for this task: .measureText() and .getTextBounds(). While both are used for text measurement, they offer distinct information and serve different purposes. Understanding the nuances of Android Paint: .measureText() vs .getTextBounds() is crucial for avoiding layout issues and achieving pixel-perfect UI. This guide will delve into each method, highlighting their differences, use cases, and how to choose the right one for your specific needs.

Deconstructing Paint.measureText(): Measuring Text Width

The Paint.measureText(String text) method is designed to calculate the exact width of a given text string. It returns a float value representing the advance width, which is the distance from the origin of the text to the point where the next character would be drawn. This method primarily considers the horizontal dimension of the text, including kerning and spacing adjustments defined by the typeface and paint settings.

Developers often use .measureText() when they only need to know how wide a piece of text will be. For instance, if you’re creating a horizontally scrolling text view or trying to ensure text doesn’t overflow a fixed-width container, this method is ideal for determining the necessary width. It’s efficient for quick width calculations and is particularly useful when you’re drawing multiple lines of text where each line’s width needs to be determined independently.

One critical aspect to remember is that .measureText() does not account for the height of the text or any leading/trailing empty space that might exist due to the font’s ascenders and descenders extending beyond the visible characters. It’s a pure horizontal measurement. For example, if you measure the text “Ay” and “Tg”, their advance widths might be similar, but their visual bounding boxes will differ significantly in height and vertical positioning relative to the baseline. This specificity makes it powerful for width-constrained layouts but insufficient for comprehensive bounding box needs.

Exploring Paint.getTextBounds(): Capturing the Full Bounding Box

In contrast to .measureText(), the Paint.getTextBounds(String text, int index, int count, Rect bounds) method provides a comprehensive bounding box for the specified text. It populates a Rect object with the smallest rectangle that encloses all the characters, relative to the text’s origin (0,0). This means the Rect will contain information about the left, top, right, and bottom edges of the text, effectively giving you both its width and height, as well as its offset from the baseline.

The Rect returned by .getTextBounds() is particularly useful when you need to know the exact pixel boundaries occupied by the text. This includes the space taken by ascenders (parts of letters like ‘h’ or ’t’ that extend above the baseline) and descenders (parts of letters like ‘p’ or ‘g’ that extend below the baseline). For example, if you’re drawing a selection highlight around text, implementing custom text selection, or calculating collision detection for text elements, .getTextBounds() provides the precise dimensions needed.

Understanding the coordinates within the Rect is vital. The top value will typically be negative (above the baseline), and the bottom value will be positive (below the baseline). The left and right values define the horizontal span. This detailed information about the text bounding box allows for highly accurate positioning and sizing of elements relative to rendered text, far beyond what a simple width measurement can offer.

Key Differences and Practical Implications

The fundamental distinction between .measureText() and .getTextBounds() lies in the scope of their measurement and the type of information they provide. While .measureText() returns a float representing only the horizontal advance, .getTextBounds() populates a Rect with the precise pixel boundaries, encompassing both width and height, relative to the text’s baseline.

When to use .measureText():

  • Calculating the maximum horizontal space a single line of text will occupy.
  • Dynamically adjusting the width of a view based on its text content.
  • Implementing text truncation (e.g., adding “…” if text exceeds a certain width).

When to use .getTextBounds():

  • Drawing a background or border tightly around text.
  • Determining the exact height needed for a text view to avoid clipping ascenders/descenders.
  • Implementing custom text layout algorithms where precise vertical positioning relative to the baseline is critical.
  • Performing hit-testing or touch detection on individual text elements.

For instance, if you want to center a piece of text horizontally within a canvas, .measureText() is perfect for determining the offset. However, if you want to draw a rectangle exactly around that text, including its ascenders and descenders, you would need .getTextBounds(). A common mistake is to try to derive text height solely from Paint.getFontMetrics(), which provides font metrics like ascent and descent, but these are general font properties, not the exact bounds for a specific string. .getTextBounds() provides the actual rendered bounds for the given string.

Infographic here
Choosing the Right Method for Your Android Custom View ------------------------------------------------------

Selecting between .measureText() and .getTextBounds() depends entirely on your specific layout requirements and the information you need. For simple horizontal alignment or width calculations, .measureText() is often sufficient and slightly more performant as it doesn’t need to compute the full bounding box. However, for any scenario involving accurate vertical positioning, drawing elements around text, or complex text rendering, .getTextBounds() is indispensable. It provides the most accurate text height and overall dimensions for the rendered string.

Consider a scenario where you’re building a custom chart view that displays data labels. If you just need to know if a label will fit horizontally next to a bar, .measureText() is enough. But if you also need to draw a rectangular background behind each label and ensure it perfectly encapsulates the text without clipping, .getTextBounds() is the method to use. This distinction is paramount for professional-grade custom UI components, ensuring your elements align perfectly and appear visually appealing.

To achieve highly precise text rendering and alignment in your Android Canvas elements, follow these steps:

  1. Determine your primary measurement need: Do you only need the width for horizontal layout, or do you require the full bounding box (width and height) for precise positioning and drawing?

  2. For width-only: Use paint.measureText(textString). This will give you the advance width suitable for horizontal spacing.

  3. For full bounding box: Initialize a Rect object and then call paint.getTextBounds(textString, 0, textString.length(), rect). The rect object will then contain the precise pixel bounds.

  4. Account for baseline: When using getTextBounds(), remember that the Rect coordinates are relative to the text’s baseline. Question & Answer :
    I’m measuring text using Paint.getTextBounds(), since I’m interested in getting both the height and width of the text to be rendered. However, the actual text rendered is always a bit wider than the .width() of the Rect information filled by getTextBounds().

    To my surprise, I tested .measureText(), and found that it returns a different (higher) value. I gave it a try, and found it correct.

    Why do they report different widths? How can I correctly obtain the height and width? I mean, I can use .measureText(), but then I wouldn’t know if I should trust the .height() returned by getTextBounds().

    As requested, here is minimal code to reproduce the problem:

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

    The output shows that the difference not only gets greater than 1 (and is no last-minute rounding error), but also seems to increase with size (I was about to draw more conclusions, but it may be entirely font-dependent):

    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 
    

    You can do what I did to inspect such problem:

    Study Android source code, Paint.java source, see both measureText and getTextBounds methods. You’d learn that measureText calls native_measureText, and getTextBounds calls nativeGetStringBounds, which are native methods implemented in C++.

    So you’d continue to study Paint.cpp, which implements both.

    native_measureText -> SkPaintGlue::measureText_CII

    nativeGetStringBounds -> SkPaintGlue::getStringBounds

    Now your study checks where these methods differ. After some param checks, both call function SkPaint::measureText in Skia Lib (part of Android), but they both call different overloaded form.

    Digging further into Skia, I see that both calls result into same computation in same function, only return result differently.

    To answer your question: Both your calls do same computation. Possible difference of result lies in fact that getTextBounds returns bounds as integer, while measureText returns float value.

    So what you get is rounding error during conversion of float to int, and this happens in Paint.cpp in SkPaintGlue::doTextBounds in call to function SkRect::roundOut.

    The difference between computed width of those two calls may be maximally 1.

    EDIT 4 Oct 2011

    What may be better than visualization. I took the effort, for own exploring, and for deserving bounty :)

    enter image description here

    This is font size 60, in red is bounds rectangle, in purple is result of measureText.

    It’s seen that bounds left part starts some pixels from left, and value of measureText is incremented by this value on both left and right. This is something called Glyph’s AdvanceX value. (I’ve discovered this in Skia sources in SkPaint.cpp)

    So the outcome of the test is that measureText adds some advance value to the text on both sides, while getTextBounds computes minimal bounds where given text will fit.

    Hope this result is useful to you.

    Testing code:

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