How to align text vertically?

JavaAndroidCanvasDrawtext

Java Problem Overview


Target: Android >= 1.6 on a pure Canvas.

Suppose I want to write a function that will draw a (width, height) large red rectangle and then draw a black Hello World text inside. I want the text to be visually in the center of the rectangle. So let's try:

void drawHelloRectangle(Canvas c, int topLeftX, 
        int topLeftY, int width, int height) {
    Paint mPaint = new Paint();
    // height of 'Hello World'; height*0.7 looks good
    int fontHeight = (int)(height*0.7);

    mPaint.setColor(COLOR_RED);
    mPaint.setStyle(Style.FILL);
    c.drawRect( topLeftX, topLeftY, topLeftX+width, topLeftY+height, mPaint);

    mPaint.setTextSize(fontHeight);
    mPaint.setColor(COLOR_BLACK);
    mPaint.setTextAlign(Align.CENTER);
    c.drawText( "Hello World", topLeftX+width/2, ????, mPaint);
}

Now I don't know what to put in drawText's argument marked by ????, i.e. I don't know how to vertically align the text.

Something like

> ???? = topLeftY + height/2 + > fontHeight/2 - fontHeight/8;

appears to work more or less ok, but there must be a better way.

Java Solutions


Solution 1 - Java

Example to centre on cx and cy:

private final Rect textBounds = new Rect(); //don't new this up in a draw method

public void drawTextCentred(Canvas canvas, Paint paint, String text, float cx, float cy){
  paint.getTextBounds(text, 0, text.length(), textBounds);
  canvas.drawText(text, cx - textBounds.exactCenterX(), cy - textBounds.exactCenterY(), paint);
}

Why doesn't height()/2f work the same?

exactCentre() = (top + bottom) / 2f.

height()/2f = (bottom - top) / 2f

These would only yield the same result when top is 0. This may be the case for some fonts at all sizes, or other fonts at some sizes, but not for all fonts at all sizes.

Solution 2 - Java

textY = topLeftY + height/2 - (mPaint.descent() + mPaint.ascent()) / 2

The distance from "baseline" to "center" should be -(mPaint.descent() + mPaint.ascent()) / 2

Solution 3 - Java

Based on steelbytes' response, the updated code would look something like:

void drawHelloRectangle(Canvas c, int topLeftX, int topLeftY, int width, int height) {
    Paint mPaint = new Paint();
    // height of 'Hello World'; height*0.7 looks good
    int fontHeight = (int)(height*0.7);

    mPaint.setColor(COLOR_RED);
    mPaint.setStyle(Style.FILL);
    c.drawRect( topLeftX, topLeftY, topLeftX+width, topLeftY+height, mPaint);

    mPaint.setTextSize(fontHeight);
    mPaint.setColor(COLOR_BLACK);
    mPaint.setTextAlign(Align.CENTER);
    String textToDraw = new String("Hello World");
    Rect bounds = new Rect();
    mPaint.getTextBounds(textToDraw, 0, textToDraw.length(), bounds);
    c.drawText(textToDraw, topLeftX+width/2, topLeftY+height/2+(bounds.bottom-bounds.top)/2, mPaint);
}

Solution 4 - Java

Since drawing text at Y means that the baseline of the text will end up Y pixels down from the origin, what you need to do when you want to center text within a rectangle of (width, height) dimensions is:

paint.setTextAlign(Paint.Align.CENTER);  // centers horizontally
canvas.drawText(text, width / 2, (height - paint.ascent()) / 2, paint);

Keep in mind that the ascent is negative (which explains the minus sign).

This does not take the descent into account, which is usually what you want (the ascent is generally the height of caps above the baseline).

Solution 5 - Java

using mPaint.getTextBounds() you can ask how big the text will be when drawn, then using that info you can calc where you want to draw it.

Solution 6 - Java

public static PointF getTextCenterToDraw(String text, RectF region, Paint paint) {
	Rect textBounds = new Rect();
	paint.getTextBounds(text, 0, text.length(), textBounds);
	float x = region.centerX() - textBounds.width() * 0.4f;
	float y = region.centerY() + textBounds.height() * 0.4f;
	return new PointF(x, y);
}

Usage:

PointF p = getTextCenterToDraw(text, rect, paint);
canvas.drawText(text, p.x, p.y, paint);

Solution 7 - Java

I stumbled on this question when trying to solve my issue, and @Weston's answer works fine with me.

In case of Kotlin:

private fun drawText(canvas: Canvas) {
    paint.textSize = 80f
    val text = "Hello!"
    val textBounds = Rect()
    paint.getTextBounds(text, 0, text.length, textBounds);
    canvas.drawText(text, cx- textBounds.exactCenterX(), cy - textBounds.exactCenterY(), paint);
    //in case of another Rect as a container:
    //canvas.drawText(text, containerRect.exactCenterX()- textBounds.exactCenterX(), containerRect.exactCenterY() - textBounds.exactCenterY(), paint);
}

Solution 8 - Java

Here is a SkiaSharp C# extension method for anyone looking for it

public static void DrawTextCenteredVertically(this SKCanvas canvas, string text, SKPaint paint, SKPoint point)
{
    var textY = point.Y + (((-paint.FontMetrics.Ascent + paint.FontMetrics.Descent) / 2) - paint.FontMetrics.Descent);
    canvas.DrawText(text, point.X, textY, paint);
}

Solution 9 - Java

private final Rect textBounds = new Rect();//No need o create again

public void drawTextCentred(Canvas canvas, Paint paint, String text, float cx, float cy, float desiredWidth) {
    final float testTextSize = 48f;
    paint.setTextSize(testTextSize);
    Rect bounds = new Rect();
    paint.getTextBounds(text, 0, text.length(), bounds);
    float desiredTextSize = testTextSize * desiredWidth / bounds.width();
    paint.setTextSize(desiredTextSize);
    paint.getTextBounds(text, 0, text.length(), textBounds);
    paint.setTextAlign(Paint.Align.CENTER);
    canvas.drawText(text, cx, cy - textBounds.exactCenterY(), paint);
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLeszekView Question on Stackoverflow
Solution 1 - JavawestonView Answer on Stackoverflow
Solution 2 - JavaQQchurchView Answer on Stackoverflow
Solution 3 - Javagauravjain0102View Answer on Stackoverflow
Solution 4 - JavaPierre-Luc PaourView Answer on Stackoverflow
Solution 5 - JavaSteelBytesView Answer on Stackoverflow
Solution 6 - JavaExterminator13View Answer on Stackoverflow
Solution 7 - JavaN. OsilView Answer on Stackoverflow
Solution 8 - JavaBenoit JadinonView Answer on Stackoverflow
Solution 9 - JavaEmre HamurcuView Answer on Stackoverflow