ChatGPT解决这个技术问题 Extra ChatGPT

Draw multi-line text to Canvas

A hopefully quick question, but I can't seem to find any examples... I'd like to write multi-line text to a custom View via a Canvas, and in onDraw() I have:

...
String text = "This is\nmulti-line\ntext";
canvas.drawText(text, 100, 100, mTextPaint);
...

I was hoping this would result in line breaks, but instead I am seeing cryptic characters where the \n would be.

Any pointers appreciated.

Paul

The documentation recommends using a Layout instead of calling Canvas.drawText directly. This Q&A shows how to use a StaticLayout to draw multiline text.

a
a54studio

I found another way using static layouts. The code is here for anyone to refer to:

TextPaint mTextPaint=new TextPaint();
StaticLayout mTextLayout = new StaticLayout(mText, mTextPaint, canvas.getWidth(), Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);

canvas.save();
// calculate x and y position where your text will be placed

textX = ...
textY = ...

canvas.translate(textX, textY);
mTextLayout.draw(canvas);
canvas.restore();

better solution by my opinion.. no need to split text to lines.. Especially convenient in case text doesn't have any line breaks at start or we don't know if it has them...
Awesome, it worked for me. Can we prevent large text going out of canvas height?
Very helpful, but when centering the StaticLayout, be careful about how you set the alignment on TextPaint(). Using TextPaing.setTextAlign(Align.CENTER) caused issues for me since different phones will do different things with this.
canvas.getWidth() should really be getWidth() - getPaddingLeft() - getPaddingRight(), to account for the view's padding. Also, note that you can calculate the StaticLayout only when your text or your view size changes and draw it without constructing a new one, which is probably better!
@Eenvincible you can check my blogpost here: skoumal.net/en/android-drawing-multiline-text-on-bitmap
D
Dave

Just iterate through each line:

int x = 100, y = 100;
for (String line: text.split("\n")) {
      canvas.drawText(line, x, y, mTextPaint);
      y += mTextPaint.descent() - mTextPaint.ascent();
}

Is there a decent way to calculate the new y position? Adding a seemingly random number doesn't make me feel very comfortable...
If you feel that ascent+decent is too small, you can add a constant gap factor, or multiply (eg by 1.5 lines) to taste.
notice that ascent is negative. You actually need descent-ascent to get the height
You can get the metrics for a selected character, e.g. font.measure("Y")
The problem with doing it yourself like this is that you can't then use things like the Paint's getTextBounds to find a bounding box. Doesn't Canvas or Paint have some way of knowing about a multiline text box? That seems like a pretty common requirement.
I
Icemanind

Unfortunately Android doesn't know what \n is. What you have to do is strip the \n and then offset the Y to get your text on the next line. So something like this:

canvas.drawText("This is", 100, 100, mTextPaint);
canvas.drawText("multi-line", 100, 150, mTextPaint);
canvas.drawText("text", 100, 200, mTextPaint);

So I'd have to break the text up into three separate chunks, then have three calls to drawText()?
Yes. I just added an example. Use String.Split to split at the '\n's and then offset each one.
Thank you so much for this idea.
Android does know what \n is, just add it in string text
S
Siddhpura Amit

I have written complete example

https://i.stack.imgur.com/O9Rnj.jpg

colors.xml

  <color name="transparentBlack">#64000000</color>

java class

 public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.amit);
        ImageView imageView = (ImageView)findViewById(R.id.imageView);
        imageView.setImageBitmap(drawTextToBitmap(this, bm, "Name: Kolala\nDate: Dec 23 2016 12:47 PM, \nLocation: 440 Banquets & Restaurents"));

    }

  public Bitmap drawTextToBitmap(Context gContext,
                                   Bitmap bitmap,
                                   String gText) {
        Resources resources = gContext.getResources();
        float scale = resources.getDisplayMetrics().density;

        android.graphics.Bitmap.Config bitmapConfig =
                bitmap.getConfig();
        // set default bitmap config if none
        if(bitmapConfig == null) {
            bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
        }
        // resource bitmaps are imutable,
        // so we need to convert it to mutable one
        bitmap = bitmap.copy(bitmapConfig, true);

        Canvas canvas = new Canvas(bitmap);
        // new antialised Paint
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // text color - #3D3D3D
        paint.setColor(Color.WHITE);
        // text size in pixels
        paint.setTextSize((int) (25 * scale));
        // text shadow
        paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);

        // draw text to the Canvas center
        Rect bounds = new Rect();

        int noOfLines = 0;
        for (String line: gText.split("\n")) {
           noOfLines++;
        }

        paint.getTextBounds(gText, 0, gText.length(), bounds);
        int x = 20;
        int y = (bitmap.getHeight() - bounds.height()*noOfLines);

        Paint mPaint = new Paint();
        mPaint.setColor(getResources().getColor(R.color.transparentBlack));
        int left = 0;
        int top = (bitmap.getHeight() - bounds.height()*(noOfLines+1));
        int right = bitmap.getWidth();
        int bottom = bitmap.getHeight();
        canvas.drawRect(left, top, right, bottom, mPaint);

        for (String line: gText.split("\n")) {
            canvas.drawText(line, x, y, paint);
            y += paint.descent() - paint.ascent();
        }

        return bitmap;
    }
}

Why would you use a loop to count lines? int noOfLines = gText.split("\n").length
n
noelicus

This is my solution that is based on @Dave's answer (thanks btw ;-) )

import android.graphics.Canvas;
import android.graphics.Paint;

public class mdCanvas
{
    private Canvas m_canvas;

    public mdCanvas(Canvas canvas)
    {
        m_canvas = canvas;
    }

    public void drawMultiline(String str, int x, int y, Paint paint)
    {
        for (String line: str.split("\n"))
        {
              m_canvas.drawText(line, x, y, paint);
              y += -paint.ascent() + paint.descent();
        }
    }
}

I tried to inherit Canvas, but it doesn't really let you. So this is an in-between class!


I tried this way.. everything is working fine except my largest line last word last character is only half showed. ?
L
Lumis

I have to add here my version which considers STROKE WIDTH as well.

void drawMultiLineText(String str, float x, float y, Paint paint, Canvas canvas) {
   String[] lines = str.split("\n");
   float txtSize = -paint.ascent() + paint.descent();       

   if (paint.getStyle() == Style.FILL_AND_STROKE || paint.getStyle() == Style.STROKE){
      txtSize += paint.getStrokeWidth(); //add stroke width to the text size
   }
   float lineSpace = txtSize * 0.2f;  //default line spacing

   for (int i = 0; i < lines.length; ++i) {
      canvas.drawText(lines[i], x, y + (txtSize + lineSpace) * i, paint);
   }
}

P
Premkumar Manipillai

it will work. i tested

 public Bitmap drawMultilineTextToBitmap(Context gContext,
                                       int gResId,
                                       String gText) {    
      // prepare canvas
      Resources resources = gContext.getResources();
      float scale = resources.getDisplayMetrics().density;
      Bitmap bitmap = BitmapFactory.decodeResource(resources, gResId);

      android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
      // set default bitmap config if none
      if(bitmapConfig == null) {
        bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
      }
      // resource bitmaps are imutable,
      // so we need to convert it to mutable one
      bitmap = bitmap.copy(bitmapConfig, true);

      Canvas canvas = new Canvas(bitmap);

      // new antialiased Paint
      TextPaint paint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
      // text color - #3D3D3D
      paint.setColor(Color.rgb(61, 61, 61));
      // text size in pixels
      paint.setTextSize((int) (14 * scale));
      // text shadow
      paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);

      // set text width to canvas width minus 16dp padding
      int textWidth = canvas.getWidth() - (int) (16 * scale);

      // init StaticLayout for text
      StaticLayout textLayout = new StaticLayout(
        gText, paint, textWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);

      // get height of multiline text
      int textHeight = textLayout.getHeight();

      // get position of text's top left corner
      float x = (bitmap.getWidth() - textWidth)/2;
      float y = (bitmap.getHeight() - textHeight)/2;

      // draw text to the Canvas center
      canvas.save();
      canvas.translate(x, y);
      textLayout.draw(canvas);
      canvas.restore();

      return bitmap;
    }

Source : http://www.skoumal.net/en/android-drawing-multiline-text-on-bitmap/


while i use Bitmap image = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.transparent_flag); it work fine but if i use a text view id insted it will not work
Thanks, it worked for the exact i wanted, but if you can help me out with editing the text in it, or sliding it to any other position, same like photo-shop does then again thanks in advance.
E
EdChum

Yes. Use canvas.getFontSpacing() as the increment. I've tried it myself out of curiosity and it works for any font-size.


I think you mean Paint.getFontSpacing
S
Suragch

try this

Paint paint1 = new Paint();
paint1.setStyle(Paint.Style.FILL);
paint1.setAntiAlias(true);
paint1.setColor(Color.BLACK);
paint1.setTextSize(15);


TextView tv = new TextView(context);
tv.setTextColor(Color.BLACK);
LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
llp.setMargins(5, 2, 0, 0); // llp.setMargins(left, top, right, bottom);
tv.setLayoutParams(llp);
tv.setTextSize(10);
String text="this is good to see you , i am the king of the team";

tv.setText(text);
tv.setDrawingCacheEnabled(true);
tv.measure(MeasureSpec.makeMeasureSpec(canvas.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(canvas.getHeight(), MeasureSpec.EXACTLY));
tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight());
canvas.drawBitmap(tv.getDrawingCache(), 5, 10, paint1);
tv.setDrawingCacheEnabled(false);

I think this is the perfect example of what NOT to do in onDraw.
@rupps Yes, it may be a complete overkill to include all of this in onDraw, but the answer doesn't tell you to do so. And the idea is genius (and it solved my issue). Screw StaticLayout and String.split!
a
androidseb

I re-used the solution proposed by GreenBee and made a function to draw some multi line text into specified bounds with the "..." at the end if a truncate happened:

public static void drawMultiLineEllipsizedText(final Canvas _canvas, final TextPaint _textPaint, final float _left,
            final float _top, final float _right, final float _bottom, final String _text) {
        final float height = _bottom - _top;

        final StaticLayout measuringTextLayout = new StaticLayout(_text, _textPaint, (int) Math.abs(_right - _left),
                Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);

        int line = 0;
        final int totalLineCount = measuringTextLayout.getLineCount();
        for (line = 0; line < totalLineCount; line++) {
            final int lineBottom = measuringTextLayout.getLineBottom(line);
            if (lineBottom > height) {
                break;
            }
        }
        line--;

        if (line < 0) {
            return;
        }

        int lineEnd;
        try {
            lineEnd = measuringTextLayout.getLineEnd(line);
        } catch (Throwable t) {
            lineEnd = _text.length();
        }
        String truncatedText = _text.substring(0, Math.max(0, lineEnd));

        if (truncatedText.length() < 3) {
            return;
        }

        if (truncatedText.length() < _text.length()) {
            truncatedText = truncatedText.substring(0, Math.max(0, truncatedText.length() - 3));
            truncatedText += "...";
        }
        final StaticLayout drawingTextLayout = new StaticLayout(truncatedText, _textPaint, (int) Math.abs(_right
                - _left), Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);

        _canvas.save();
        _canvas.translate(_left, _top);
        drawingTextLayout.draw(_canvas);
        _canvas.restore();
    }

When the text is truncated, your code may cut an entire word that fit the space as well. So here is a small suggestion to improve your code: replace the three characters "..." by only one containing the three dots: "…" (the … code in HTML). You can then remove only one char (which is often a space) instead of three, and keep your word uncut : truncatedText = truncatedText.substring(0, Math.max(0, truncatedText.length() - 1));
R
Roman

Solution without StaticLayout

//Get post text
    String text = post.getText();

    //Get weight of space character in px
    float spaceWeight = paint.measureText(" ");

    //Start main algorithm of drawing words on canvas
    //Split text to words
    for (String line : text.split(" ")) {
        //If we had empty space just continue
        if (line.equals("")) continue;
        //Get weight of the line
        float lineWeight = paint.measureText(line);
        //If our word(line) doesn't have any '\n' we do next
        if (line.indexOf('\n') == -1) {
            //If word can fit into current line
            if (cnv.getWidth() - pxx - defaultMargin >= lineWeight) {
                //Draw text
                cnv.drawText(line, pxx, pxy, paint);
                //Move start x point to word weight + space weight
                pxx += lineWeight + spaceWeight;
            } else {
                //If word can't fit into current line
                //Move x point to start
                //Move y point to the next line
                pxx = defaultMargin;
                pxy += paint.descent() - paint.ascent();
                //Draw
                cnv.drawText(line, pxx, pxy, paint);
                //Move x point to word weight + space weight
                pxx += lineWeight + spaceWeight;
            }
            //If line contains '\n'
        } else {
            //If '\n' is on the start of the line
            if (line.indexOf('\n') == 0) {
                pxx = defaultMargin;
                pxy += paint.descent() - paint.ascent();
                cnv.drawText(line.replaceAll("\n", ""), pxx, pxy, paint);
                pxx += lineWeight + spaceWeight;
            } else {
                //If '\n' is somewhere in the middle
                //and it also can contain few '\n'
                //Split line to sublines
                String[] subline = line.split("\n");
                for (int i = 0; i < subline.length; i++) {
                    //Get weight of new word
                    lineWeight = paint.measureText(subline[i]);
                    //If it's empty subline that's mean that we have '\n'
                    if (subline[i].equals("")) {
                        pxx = defaultMargin;
                        pxy += paint.descent() - paint.ascent();
                        cnv.drawText(subline[i], pxx, pxy, paint);
                        continue;
                    }
                    //If we have only one word
                    if (subline.length == 1 && i == 0) {
                        if (cnv.getWidth() - pxx >= lineWeight) {
                            cnv.drawText(subline[0], pxx, pxy, paint);
                            pxx = defaultMargin;
                            pxy += paint.descent() - paint.ascent();
                        } else {
                            pxx = defaultMargin;
                            pxy += paint.descent() - paint.ascent();
                            cnv.drawText(subline[0], pxx, pxy, paint);
                            pxx = defaultMargin;
                            pxy += paint.descent() - paint.ascent();
                        }
                        continue;
                    }
                    //If we have set of words separated with '\n'
                    //it is the first word
                    //Make sure we can put it into current line
                    if (i == 0) {
                        if (cnv.getWidth() - pxx >= lineWeight) {
                            cnv.drawText(subline[0], pxx, pxy, paint);
                            pxx = defaultMargin;
                        } else {
                            pxx = defaultMargin;
                            pxy += paint.descent() - paint.ascent();
                            cnv.drawText(subline[0], pxx, pxy, paint);
                            pxx = defaultMargin;
                        }
                    } else {
                        pxx = defaultMargin;
                        pxy += paint.descent() - paint.ascent();
                        cnv.drawText(subline[i], pxx, pxy, paint);
                        pxx += lineWeight + spaceWeight;
                    }
                }

            }
        }
    }

You are awesome, man! Great job! I really love it. Could you please help to extend your code to be able handle the next page once the limit of the first page is reached? In My app I use canvas to create a PDF (A4 595x842). I could adjust the width of page, but in case of a big text, I need to go on next page and so on. Many thanks in advance.
P
Peter Griffin

I worked with what I had, which already converted single lines to canvases, and I worked off Lumis's answer, and I ended up with this. The 1.3 and 1.3f are meant as padding between lines, relative to the size of the font.

public static Bitmap getBitmapFromString(final String text, final String font, int textSize, final int textColor)
{
    String lines[] = text.split("\n");
    textSize = getRelX(textSize);  //a method in my app that adjusts the font size relative to the screen size
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setTextSize(textSize);
    paint.setColor(textColor);
    paint.setTextAlign(Paint.Align.LEFT);
    Typeface face = Typeface.createFromAsset(GameActivity.getContext().getAssets(),GameActivity.getContext().getString(R.string.font) + font + GameActivity.getContext().getString(R.string.font_ttf));
    paint.setTypeface(face);
    float baseline = -paint.ascent(); // ascent() is negative
    int width = (int) (paint.measureText(text) + 0.5f); // round
    int height = (int) (baseline + paint.descent() + 0.5f);
    Bitmap image = Bitmap.createBitmap(width, (int)(height * 1.3 * lines.length), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(image);
    for (int i = 0; i < lines.length; ++i)
    {
        canvas.drawText(lines[i], 0, baseline + textSize * 1.3f * i, paint);
    }
    return image;
}

S
Sajjad

I faced similar problem. but I should returned the path of text. you can draw this path on Canvas. this is my code. I use Break Text. and path.op

           public Path createClipPath(int width, int height) {
            final Path path = new Path();
            if (textView != null) {
                mText = textView.getText().toString();
                mTextPaint = textView.getPaint();
                float text_position_x = 0;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                    text_position_x = findTextBounds(textView).left;

                }
                boolean flag = true;
                int line = 0;
                int startPointer = 0;
                int endPointer = mText.length();

                while (flag) {
                    Path p = new Path();
                    int breakText = mTextPaint.breakText(mText.substring(startPointer), true, width, null);
                    mTextPaint.getTextPath(mText, startPointer, startPointer + breakText, text_position_x,
                            textView.getBaseline() + mTextPaint.getFontSpacing() * line, p);
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                        path.op(p, Path.Op.UNION);
                    }
                    endPointer -= breakText;
                    startPointer += breakText;
                    line++;
                    if (endPointer == 0) {
                        flag = false;
                    }
                }

            }
            return path;
        }

and for finding text bound I used this function

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
private Rect findTextBounds(TextView textView) {
    // Force measure of text pre-layout.
    textView.measure(0, 0);
    String s = (String) textView.getText();

    // bounds will store the rectangle that will circumscribe the text.
    Rect bounds = new Rect();
    Paint textPaint = textView.getPaint();

    // Get the bounds for the text. Top and bottom are measured from the baseline. Left
    // and right are measured from 0.
    textPaint.getTextBounds(s, 0, s.length(), bounds);
    int baseline = textView.getBaseline();
    bounds.top = baseline + bounds.top;
    bounds.bottom = baseline + bounds.bottom;
    int startPadding = textView.getPaddingStart();
    bounds.left += startPadding;

    // textPaint.getTextBounds() has already computed a value for the width of the text,
    // however, Paint#measureText() gives a more accurate value.
    bounds.right = (int) textPaint.measureText(s, 0, s.length()) + startPadding;
    return bounds;
}

R
Ravi.Dudi

For Kotlin Users. Multiline text can be created using StaticLayout. Found a great explanation and how to use it as an extension function here. https://medium.com/over-engineering/drawing-multiline-text-to-canvas-on-android-9b98f0bfa16a


a
aboger

This is my solution. It is not perfect, but worked for me.

public static Bitmap textAsBitmap(String text, float textSize, int textColor) {
    int lines = 1;
    String lineString1 = "", lineString2 = "";
    String[] texts = text.split(" ");
    if (texts.length > 2) {
        for (int i = 0; i < 2; i++) {
            lineString1 = lineString1.concat(texts[i] + " ");
        }
        for (int i = 2; i < texts.length; i++) {
            lineString2 = lineString2.concat(texts[i] + "");
        }
    } else {
        lineString1 = text;
    }
    lineString1 = lineString1.trim();
    lineString2 = lineString2.trim();

    String[] lastText = new String[2];
    lastText[0] = lineString1;
    if (!lineString2.equals("")) {
        lines = 2;
        lastText[1] = lineString2;
    }

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setTextSize(textSize);
    paint.setColor(textColor);
    paint.setTextAlign(Paint.Align.LEFT);
    float baseline = -paint.ascent(); // ascent() is negative
    String maxLengthText = "";
    if (lines == 2) {
        if (lineString1.length() > lineString2.length()) {
            maxLengthText = maxLengthText.concat(lineString1);
        } else {
            maxLengthText = maxLengthText.concat(lineString2);
        }
    } else {
        maxLengthText = maxLengthText.concat(text);
    }
    int width = (int) (paint.measureText(maxLengthText) + 0.5f); // round
    int height = (int) ((baseline + paint.descent() + 0.5f) * lines);
    Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(image);

    for (int i = 0; i < lines; i++) {
        canvas.drawText(lastText[i], 0, baseline, paint);
        baseline *= lines;
    }

    return image;
}

B
BeChris 100

I designed a better way (I can't really say if it's better or not, but this method should be easy) for multi-line text in a canvas, like in a SurfaceView.

Here would be the code:

public class MultiLineText implements ObjectListener {

    private String[] lines;
    private float x, y, textSize;
    private int textColor;

    private float currentY;

    public MultiLineText(String[] lines, float x, float y, float textSize, int textColor) {
        this.lines = lines;
        this.x = x;
        this.y = y;
        this.textSize = textSize;
        this.textColor = textColor;
    }

    @Override
    public void draw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(textColor);
        paint.setTextSize(textSize);

        currentY = y;

        for (int i = 0; i < lines.length; i++) {
            if (i == 0)
                canvas.drawText(lines[i], x, y, paint);
            else {
                currentY = currentY + textSize;
                canvas.drawText(lines[i], x, currentY, paint);
            }
        }
    }

    @Override
    public void update() {

    }
}

Import the 2 classes with import android.graphics.Canvas; and import android.graphics.Paint; to make sure no errors can occur.

On the easy hand, create an Interface Class named "ObjectListener" (or whatever you want to call it, just change the name then), and add two following lines of code:

void draw(Canvas canvas);

void update();

To implement this, use this code in the View or your Renderer on draw(Canvas canvas) method:

new MultiLineText(new String[]{
        "This is a multi-line text.",
        "It's setup is basic. Just do the following code,",
        "and you would be done."
}, 150, 150, 32, Color.WHITE).draw(canvas);

Sorry, I just wanted to implement this text, so yeah... You can change the X and Y coordinates from 150 to your liking. A text Size of 26 is readable, and it is not too big because Canvas renders in a small text by default.


L
Leonid Ustenko

In addition to drawing multiline text, one may struggle with getting the multiline text bounds (for instance in order to align it on canvas).
Default paint.getTextBounds() will not work in this case as it will measure the only line.

For convenience, I created these 2 extension functions: one for drawing multiline text and the other is for getting text bounds.

private val textBoundsRect = Rect()

/**
 * Draws multi line text on the Canvas with origin at (x,y), using the specified paint. The origin is interpreted
 * based on the Align setting in the paint.
 *
 * @param text The text to be drawn
 * @param x The x-coordinate of the origin of the text being drawn
 * @param y The y-coordinate of the baseline of the text being drawn
 * @param paint The paint used for the text (e.g. color, size, style)
 */
fun Canvas.drawTextMultiLine(text: String, x: Float, y: Float, paint: Paint) {
    var lineY = y
    for (line in text.split("\n")) {
        drawText(line, x, lineY, paint)
        lineY += paint.descent().toInt() - paint.ascent().toInt()
    }
}

/**
 * Retrieve the text boundary box, taking into account line breaks [\n] and store to [boundsRect].
 *
 * Return in bounds (allocated by the caller [boundsRect] or default mutable [textBoundsRect]) the smallest rectangle that
 * encloses all of the characters, with an implied origin at (0,0).
 *
 * @param text string to measure and return its bounds
 * @param start index of the first char in the string to measure. By default is 0.
 * @param end 1 past the last char in the string to measure. By default is test length.
 * @param boundsRect rect to save bounds. Note, you may not supply it. By default, it will apply values to the mutable [textBoundsRect] and return it.
 * In this case it will be changed by each new this function call.
 */
fun Paint.getTextBoundsMultiLine(
    text: String,
    start: Int = 0,
    end: Int = text.length,
    boundsRect: Rect = textBoundsRect
): Rect {
    getTextBounds(text, start, end, boundsRect)
    val linesCount = text.split("\n").size
    val allLinesHeight = (descent().toInt() - ascent().toInt()) * linesCount
    boundsRect.bottom = boundsRect.top + allLinesHeight
    return boundsRect
}

Now using it is as easy as that: For drawing multiline text:

canvas.drawTextMultiLine(text, x, y, yourPaint)

For measuring text:

val bounds = yourPaint.getTextBoundsMultiLine(text)

In this case, it will measure all text from the start to the end and with using of default once allocated (mutable) Rect. You may play around with passing extra parameters for extra flexibility.


T
Thunderstick

My example with Dynamic Text Sizing and spacing, Works great for me...

public Bitmap fontTexture(String string, final Context context) {
    float text_x = 512;
    float text_y = 512;
    final float scale = context.getResources().getDisplayMetrics().density;

    int mThreshold = (int) (THRESHOLD_DIP * scale + 0.5f);

    String[] splited = string.split("\\s+");
    double longest = 0;
    for(String s:splited){
        if (s.length() > longest) {
            longest = s.length();
        }
    }
    if(longest > MAX_STRING_LENGTH) {
        double ratio = (double) MAX_STRING_LENGTH / longest;
        mThreshold = (int) ((THRESHOLD_DIP * ((float) ratio)) * scale + 0.5f);
    }

    Bitmap bitmap = Bitmap.createBitmap(1024, 1024, Bitmap.Config.ARGB_8888);

    Canvas canvas = new Canvas(bitmap);

    Typeface font = Typeface.createFromAsset(context.getAssets(),
            "fonts/dotted_font.ttf");

    TextPaint mTextPaint=new TextPaint();
    mTextPaint.setColor(Color.YELLOW);
    mTextPaint.setTextAlign(Paint.Align.CENTER);
    mTextPaint.setTextSize(mThreshold);
    mTextPaint.setTypeface(font);
    StaticLayout mTextLayout = new StaticLayout(string, mTextPaint, canvas.getWidth(), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);

    canvas.save();

    canvas.translate(text_x, text_y);
    mTextLayout.draw(canvas);
    canvas.restore();


    return bitmap;
}