将多行文本绘制到画布

一个希望很快的问题,但我似乎找不到任何例子... 我想写多行文本到一个自定义 View通过 Canvas,在 onDraw()我有:

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

我希望这会导致换行,但相反,我看到神秘的字符 \n将是。

感谢你的指点。

保罗

102718 次浏览

不幸的是,Android 并不知道 \n是什么。你需要做的就是去掉 \n,然后偏移 Y,在下一行得到你的文本。比如说:

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

只要遍历每一行:

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

我发现了另一种使用静态布局的方法,代码在这里供大家参考:

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

是的。使用 canvas.getFontSpacing()作为增量。出于好奇,我自己也试过,它适用于任何字体大小。

这是我的解决方案,是基于@Dave 的回答(顺便说一句,谢谢; ——)

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

我尝试继承 Canvas,但是它不允许,所以这是一个介于两者之间的类!

我必须在这里添加我的版本,考虑中风宽度以及。

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

试试这个

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

我重复使用了 GreenBee 提出的解决方案,并且创建了一个函数来将一些多行文本绘制到指定的边界,如果发生截断,则在最后使用“ ...”:

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

我的例子与动态文本大小和间距,工程伟大的我..。

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

会有用的,我测试过了

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

资料来源: http://www.skoumal.net/en/android-drawing-multiline-text-on-bitmap/

我已经写了完整的例子

enter image description here

Xml

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

Java 类

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

没有 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;
}
}


}
}
}

我利用已有的资料,已经把单线转换成了画布,我利用 Lumis 的答案,最后得到了这个。1.3和1.3 f 表示行之间相对于字体大小的填充。

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

我遇到了类似的问题。但是我应该返回文本的路径。 你可以在画布上画这条路。 这是我的代码,我使用中断文本和路径

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

为了查找文本绑定,我使用了这个函数

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

科特林用户。 可以使用 静态布局创建多行文本。找到了一个很好的解释,以及如何使用它作为一个扩展函数在这里。 Https://medium.com/over-engineering/drawing-multiline-text-to-canvas-on-android-9b98f0bfa16a

除了绘制多行文本,一个人可能挣扎得到的 多行文本边界(例如,为了在画布上对齐它)。
默认的 paint.getTextBounds()在这种情况下不起作用,因为它将测量唯一的行。

为了方便起见,我创建了这两个扩展函数: 一个用于绘制多行文本,另一个用于获取文本边界。

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
}

现在使用它就是这么简单: 用于绘制多行文本:

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

测量文字:

Val bound = yourPaint.getTextbound sMultiLine (text)

在这种情况下,它将测量从开始到结束的所有文本,并使用默认的一次分配(可变) Rect。 您可以通过传递额外的参数来获得额外的灵活性。

这是我的解决方案,虽然不完美,但对我很有效。

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

我为画布中的多行文本设计了一种更好的方法(我不能说它是否更好,但是这种方法应该很简单) ,就像在 SurfaceView 中一样。

密码如下:

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 android.graphics.Canvas;import android.graphics.Paint;导入这两个类,以确保不会发生错误。

简单来说,创建一个名为“ ObjectListener”的 Interface Class (或者不管你想怎么称呼它,只要改变它的名字就行了) ,然后添加两行代码:

void draw(Canvas canvas);


void update();

要实现这一点,请在视图或 draw(Canvas canvas)方法的渲染器中使用此代码:

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

抱歉,我只是想实现这条短信,所以..。 您可以将 X 和 Y 坐标从150更改为您喜欢的值。文本大小为26是可读的,并且不会太大,因为默认情况下,Canvas 呈现的是一个小文本。

希望这对你有所帮助,你可以设置文本字体,在画布上对齐文本。

        Typeface font = ResourcesCompat.getFont(this, R.font.sf_pro_text_bold);
Bitmap textBm = BitmapUtils.createBitmapFromText("This is longgggggg texttttttttttttttttttt \n This is line breakkkkkkkkkkk"
, width  // Container width
, Color.BLACK
, 40
, font
, TextAlign.CENTER
, 10f
, true);


public enum TextAlign {
CENTER,
LEFT,
RIGHT
}
/**
* Create bit map from text with auto line break
* */
public static Bitmap createBitmapFromText(String text, int containerWidth, @ColorInt int textColor, int textSize
, @Nullable Typeface textFont, @Nullable TextAlign textAlign
, @Nullable Float lineSpace, @Nullable Boolean trimLine) {


Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTypeface(textFont);
textPaint.setTextSize(textSize);
textPaint.setColor(textColor);


ArrayList<String> linesText = splitTextToMultiLine(text, containerWidth, textSize, textFont);


if(lineSpace == null) {
lineSpace = 10f;
}


int bmWidth = containerWidth;
int bmHeight = 0;
for(String line : linesText) {
int lineHeight = (int)(calculateTextHeightFromFontSize(line, textSize, textFont)*1.1);
bmHeight += (lineHeight + lineSpace);
}




Bitmap result = Bitmap.createBitmap(bmWidth, bmHeight, Bitmap.Config.ARGB_8888);


Canvas canvas = new Canvas(result);


if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
TextPaint staticLayoutPaint = new TextPaint();
staticLayoutPaint.setTextSize(textSize);
staticLayoutPaint.setTypeface(textFont);


Layout.Alignment align = Layout.Alignment.ALIGN_CENTER;
if(textAlign == TextAlign.LEFT) {
align = Layout.Alignment.ALIGN_NORMAL;
} else if(textAlign == TextAlign.RIGHT) {
align = Layout.Alignment.ALIGN_OPPOSITE;
}


StaticLayout.Builder staticLayoutBuilder =  StaticLayout.Builder
.obtain("", 0, 0, staticLayoutPaint, containerWidth)
.setAlignment(align)
.setLineSpacing(lineSpace, 1)
.setText(text);
staticLayoutBuilder.build().draw(canvas);


} else {
float xOffset = 0;
float yOffset = -textPaint.ascent(); // ascent() is negative;


for(String lineText : linesText) {
if(trimLine) {
lineText = lineText.trim();
}
if(textAlign == TextAlign.RIGHT) {
xOffset = containerWidth - calculateTextWidthFromFontSize(lineText, textSize, textFont);
} else if (textAlign == TextAlign.CENTER){
xOffset = (containerWidth - calculateTextWidthFromFontSize(lineText, textSize, textFont)) / 2;
}


canvas.drawText(lineText, xOffset, yOffset, textPaint);


float nextLineOffset = calculateTextHeightFromFontSize(lineText, textSize, textFont) + lineSpace;
yOffset += nextLineOffset;
}
}


return result;
}


private static int calculateTextWidthFromFontSize(String text, int textSize, @Nullable Typeface textFont) {
String singleChar = text;


Rect bounds = new Rect();
Paint paint = new Paint();
paint.setTextSize(textSize);
paint.setTypeface(textFont);
paint.getTextBounds(singleChar, 0, singleChar.length(), bounds);


return bounds.width();
}


private static int calculateTextHeightFromFontSize(String text, int textSize, @Nullable Typeface textFont) {
String singleChar = text;


Rect bounds = new Rect();
Paint paint = new Paint();
paint.setTextSize(textSize);
paint.setTypeface(textFont);
paint.getTextBounds(singleChar, 0, singleChar.length(), bounds);


return bounds.height();
}


private static ArrayList<String> splitTextToMultiLine(String input, int containerWidth, int textSize, @Nullable Typeface textFont) {
ArrayList<String> result = new ArrayList<>();


//Split String by line break first
String[] multiLine = input.split("\n");


for(String line : multiLine) {
result.addAll(splitLongStringToMultiLine(line, containerWidth, textSize, textFont));
}


return result;
}


/**
* Split long string (without line break) to multi line
* */
private static ArrayList<String> splitLongStringToMultiLine(String input, int containerWidth, int textSize, @Nullable Typeface textFont) {
ArrayList<String> result = new ArrayList<>();
//Reduce loop performance
int singleTextWidth = calculateTextWidthFromFontSize("A", textSize, textFont);
int minTextPerLine = containerWidth/singleTextWidth;


if(minTextPerLine >= input.length()
|| calculateTextWidthFromFontSize(input, textSize, textFont) < containerWidth) {
result.add(input);
return result;
}


int startSplit = 0;


while (startSplit < input.length()) {
int addedTextPerLine = 0;


if(startSplit + minTextPerLine < input.length()) {
int endPos = startSplit + minTextPerLine;
String availableChild = input.substring(startSplit, endPos);


//Detect more character in line
int updatePos = endPos;
while (updatePos < input.length()
&& calculateTextWidthFromFontSize(availableChild, textSize, textFont) < containerWidth) {


availableChild = input.substring(startSplit, updatePos);


addedTextPerLine++;
updatePos = endPos + addedTextPerLine;
}


//Detect last space char and split
int spaceIndex = availableChild.lastIndexOf(" ");
if(spaceIndex > 0) {
//Update split point
startSplit -= (availableChild.length() - spaceIndex);
availableChild = availableChild.substring(0, spaceIndex);
} else{
//No space found ->
}


result.add(availableChild);
} else {
//Last line
String child = input.substring(startSplit);
result.add(child);
}


startSplit += minTextPerLine;
startSplit += addedTextPerLine;
addedTextPerLine = 0;
}




if(result.size() == 0) {
//Cheat
result.add(input);
}


return result;
}