Merge pull request #12137 from WenhaiLin/v3-text-line-height

Fixed the text line height calculation is wrong with system font on Android.
This commit is contained in:
子龙山人 2015-06-03 16:24:38 +08:00
commit 883f250f6a
1 changed files with 81 additions and 361 deletions

View File

@ -26,33 +26,30 @@ package org.cocos2dx.lib;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.LinkedList;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Log;
public class Cocos2dxBitmap {
public final class Cocos2dxBitmap {
// ===========================================================
// Constants
// ===========================================================
/* The values are the same as cocos2dx/platform/CCImage.h. */
private static final int HORIZONTALALIGN_LEFT = 1;
private static final int HORIZONTALALIGN_RIGHT = 2;
private static final int HORIZONTALALIGN_CENTER = 3;
private static final int VERTICALALIGN_TOP = 1;
private static final int VERTICALALIGN_BOTTOM = 2;
private static final int VERTICALALIGN_CENTER = 3;
private static final int HORIZONTAL_ALIGN_LEFT = 1;
private static final int HORIZONTAL_ALIGN_RIGHT = 2;
private static final int HORIZONTAL_ALIGN_CENTER = 3;
private static final int VERTICAL_ALIGN_TOP = 1;
private static final int VERTICAL_ALIGN_BOTTOM = 2;
private static final int VERTICAL_ALIGN_CENTER = 3;
// ===========================================================
// Fields
@ -60,10 +57,6 @@ public class Cocos2dxBitmap {
private static Context sContext;
// ===========================================================
// Constructors
// ===========================================================
// ===========================================================
// Getter & Setter
// ===========================================================
@ -83,129 +76,98 @@ public class Cocos2dxBitmap {
private static native void nativeInitBitmapDC(final int width,
final int height, final byte[] pixels);
/**
* @param width
* the width to draw, it can be 0
* @param height
* the height to draw, it can be 0
*/
public static void createTextBitmap(String string, final String fontName,
final int fontSize, final int alignment, final int width,
final int height) {
createTextBitmapShadowStroke( string.getBytes(), fontName, fontSize, 255, 255, 255, 255, // text font and color
alignment, width, height, // alignment and size
false, 0.0f, 0.0f, 0.0f, 0.0f, // no shadow
false, 255, 255, 255, 255, 0.0f); // no stroke
}
public static boolean createTextBitmapShadowStroke(byte[] bytes, final String fontName, int fontSize,
int fontTintR, int fontTintG, int fontTintB, int fontTintA,
int alignment, int width, int height,
boolean shadow, float shadowDX, float shadowDY, float shadowBlur, float shadowOpacity,
boolean stroke, int strokeR, int strokeG, int strokeB, int strokeA, float strokeSize) {
String string;
if (bytes == null || bytes.length == 0) {
string = "";
return false;
} else {
string = new String(bytes);
}
final int horizontalAlignment = alignment & 0x0F;
final int verticalAlignment = (alignment >> 4) & 0x0F;
string = Cocos2dxBitmap.refactorString(string);
final Paint paint = Cocos2dxBitmap.newPaint(fontName, fontSize, horizontalAlignment);
// if the first word width less than designed width, it means no words to show
if(0 != width)
{
final int firstWordWidth = (int) Math.ceil(paint.measureText(string, 0,1));
if (firstWordWidth > width)
{
Log.w("createTextBitmapShadowStroke warning:","the input width is less than the width of the pString's first word\n");
return false;
}
Layout.Alignment hAlignment = Layout.Alignment.ALIGN_NORMAL;
int horizontalAlignment = alignment & 0x0F;
switch (horizontalAlignment) {
case HORIZONTAL_ALIGN_CENTER:
hAlignment = Layout.Alignment.ALIGN_CENTER;
break;
case HORIZONTAL_ALIGN_RIGHT:
hAlignment = Layout.Alignment.valueOf("ALIGN_RIGHT");
break;
case HORIZONTAL_ALIGN_LEFT:
hAlignment = Layout.Alignment.valueOf("ALIGN_LEFT");
break;
default:
break;
}
// set the paint color
paint.setARGB(fontTintA, fontTintR, fontTintG, fontTintB);
TextPaint paint = Cocos2dxBitmap.newPaint(fontName, fontSize, horizontalAlignment);
if (stroke) {
paint.setStyle(TextPaint.Style.STROKE);
paint.setStrokeWidth(strokeSize);
}
final TextProperty textProperty = Cocos2dxBitmap.computeTextProperty(string, width, height, paint);
final int bitmapTotalHeight = (height == 0 ? textProperty.mTotalHeight: height);
// padding needed when using shadows (not used otherwise)
float bitmapPaddingX = 0.0f;
float bitmapPaddingY = 0.0f;
float renderTextDeltaX = 0.0f;
float renderTextDeltaY = 0.0f;
if (0 == textProperty.mMaxWidth || 0 == bitmapTotalHeight)
{
Log.w("createTextBitmapShadowStroke warning:","textProperty MaxWidth is 0 or bitMapTotalHeight is 0\n");
int maxWidth = width;
if (maxWidth <= 0) {
maxWidth = (int)Math.ceil( StaticLayout.getDesiredWidth(string, paint));
}
StaticLayout staticLayout = new StaticLayout(string, paint, maxWidth , hAlignment,1.0f,0.0f,false);
int layoutWidth = staticLayout.getWidth();
int layoutHeight = staticLayout.getLineTop(staticLayout.getLineCount());
int bitmapWidth = Math.max(layoutWidth, width);
int bitmapHeight = layoutHeight;
if (height > 0) {
bitmapHeight = height;
}
if (bitmapWidth == 0 || bitmapHeight == 0) {
return false;
}
final Bitmap bitmap = Bitmap.createBitmap(textProperty.mMaxWidth + (int)bitmapPaddingX,
bitmapTotalHeight + (int)bitmapPaddingY, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
int offsetX = 0;
if (horizontalAlignment == HORIZONTAL_ALIGN_CENTER) {
offsetX = (bitmapWidth - layoutWidth) / 2;
}
else if (horizontalAlignment == HORIZONTAL_ALIGN_RIGHT) {
offsetX = bitmapWidth - layoutWidth;
}
// Draw string.
final FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
// draw again with stroke on if needed
int offsetY = 0;
int verticalAlignment = (alignment >> 4) & 0x0F;
switch (verticalAlignment)
{
case VERTICAL_ALIGN_CENTER:
offsetY = (bitmapHeight - layoutHeight) / 2;
break;
case VERTICAL_ALIGN_BOTTOM:
offsetY = bitmapHeight - layoutHeight;
break;
}
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.translate(offsetX, offsetY);
if ( stroke )
{
final Paint paintStroke = Cocos2dxBitmap.newPaint(fontName, fontSize, horizontalAlignment);
paintStroke.setStyle(Paint.Style.STROKE);
paintStroke.setStrokeWidth(strokeSize);
paintStroke.setARGB(strokeA, strokeR, strokeG, strokeB);
int x = 0;
int y = Cocos2dxBitmap.computeY(fontMetricsInt, height, textProperty.mTotalHeight, verticalAlignment);
final String[] lines2 = textProperty.mLines;
for (final String line : lines2) {
x = Cocos2dxBitmap.computeX(line, textProperty.mMaxWidth, horizontalAlignment);
canvas.drawText(line, x + renderTextDeltaX, y + renderTextDeltaY, paintStroke);
canvas.drawText(line, x + renderTextDeltaX, y + renderTextDeltaY, paint);
y += textProperty.mHeightPerLine;
}
paint.setARGB(strokeA, strokeR, strokeG, strokeB);
staticLayout.draw(canvas);
}
else
{
int x = 0;
int y = Cocos2dxBitmap.computeY(fontMetricsInt, height, textProperty.mTotalHeight, verticalAlignment);
final String[] lines = textProperty.mLines;
for (final String line : lines) {
x = Cocos2dxBitmap.computeX(line, textProperty.mMaxWidth, horizontalAlignment);
canvas.drawText(line, x + renderTextDeltaX, y + renderTextDeltaY, paint);
y += textProperty.mHeightPerLine;
}
}
Cocos2dxBitmap.initNativeObject(bitmap);
paint.setStyle(TextPaint.Style.FILL);
paint.setARGB(fontTintA, fontTintR, fontTintG, fontTintB);
staticLayout.draw(canvas);
Cocos2dxBitmap.initNativeObject(bitmap);
return true;
}
private static Paint newPaint(final String fontName, final int fontSize,
private static TextPaint newPaint(final String fontName, final int fontSize,
final int horizontalAlignment) {
final Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(fontSize);
final TextPaint paint = new TextPaint();
paint.setTextSize(fontSize);
paint.setAntiAlias(true);
// Set type face for paint, now it support .ttf file.
@ -225,227 +187,8 @@ public class Cocos2dxBitmap {
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
}
switch (horizontalAlignment) {
case HORIZONTALALIGN_CENTER:
paint.setTextAlign(Align.CENTER);
break;
case HORIZONTALALIGN_RIGHT:
paint.setTextAlign(Align.RIGHT);
break;
case HORIZONTALALIGN_LEFT:
default:
paint.setTextAlign(Align.LEFT);
break;
}
return paint;
}
private static TextProperty computeTextProperty(final String string,
final int width, final int height, final Paint paint) {
final FontMetricsInt fm = paint.getFontMetricsInt();
final int h = (int) Math.ceil(fm.bottom - fm.top);
int maxContentWidth = 0;
final String[] lines = Cocos2dxBitmap.splitString(string, width,
height, paint);
if (width != 0) {
maxContentWidth = width;
} else {
// Compute the max width.
int temp = 0;
for (final String line : lines) {
temp = (int) Math.ceil(paint.measureText(line, 0,
line.length()));
if (temp > maxContentWidth) {
maxContentWidth = temp;
}
}
}
return new TextProperty(maxContentWidth, h, lines);
}
private static int computeX(final String text, final int maxWidth,
final int horizontalAlignment) {
int ret = 0;
switch (horizontalAlignment) {
case HORIZONTALALIGN_CENTER:
ret = maxWidth / 2;
break;
case HORIZONTALALIGN_RIGHT:
ret = maxWidth;
break;
case HORIZONTALALIGN_LEFT:
default:
break;
}
return ret;
}
private static int computeY(final FontMetricsInt fontMetricsInt,
final int constrainHeight, final int totalHeight,
final int verticalAlignment) {
int y = -fontMetricsInt.top;
if (constrainHeight > totalHeight) {
switch (verticalAlignment) {
case VERTICALALIGN_TOP:
y = -fontMetricsInt.top;
break;
case VERTICALALIGN_CENTER:
y = -fontMetricsInt.top + (constrainHeight - totalHeight)
/ 2;
break;
case VERTICALALIGN_BOTTOM:
y = -fontMetricsInt.top + (constrainHeight - totalHeight);
break;
default:
break;
}
}
return y;
}
/*
* If maxWidth or maxHeight is not 0, split the string to fix the maxWidth
* and maxHeight.
*/
private static String[] splitString(final String string,
final int maxWidth, final int maxHeight, final Paint paint) {
final String[] lines = string.split("\\n");
String[] ret = null;
final FontMetricsInt fm = paint.getFontMetricsInt();
final int heightPerLine = (int) Math.ceil(fm.bottom - fm.top);
final int maxLines = maxHeight / heightPerLine;
if (maxWidth != 0) {
final LinkedList<String> strList = new LinkedList<String>();
for (final String line : lines) {
/*
* The width of line is exceed maxWidth, should divide it into
* two or more lines.
*/
final int lineWidth = (int) Math.ceil(paint
.measureText(line));
if (lineWidth > maxWidth) {
strList.addAll(Cocos2dxBitmap.divideStringWithMaxWidth(
line, maxWidth, paint));
} else {
strList.add(line);
}
// Should not exceed the max height.
if (maxLines > 0 && strList.size() >= maxLines) {
break;
}
}
// Remove exceeding lines.
if (maxLines > 0 && strList.size() > maxLines) {
while (strList.size() > maxLines) {
strList.removeLast();
}
}
ret = new String[strList.size()];
strList.toArray(ret);
} else if (maxHeight != 0 && lines.length > maxLines) {
/* Remove exceeding lines. */
final LinkedList<String> strList = new LinkedList<String>();
for (int i = 0; i < maxLines; i++) {
strList.add(lines[i]);
}
ret = new String[strList.size()];
strList.toArray(ret);
} else {
ret = lines;
}
return ret;
}
private static LinkedList<String> divideStringWithMaxWidth(
final String string, final int maxWidth, final Paint paint) {
final int charLength = string.length();
int start = 0;
int tempWidth = 0;
final LinkedList<String> strList = new LinkedList<String>();
// Break a String into String[] by the width & should wrap the word.
for (int i = 1; i <= charLength; ++i) {
tempWidth = (int) Math.ceil(paint.measureText(string, start,
i));
if (tempWidth >= maxWidth) {
final int lastIndexOfSpace = string.substring(0, i)
.lastIndexOf(" ");
if (lastIndexOfSpace != -1 && lastIndexOfSpace > start) {
// Should wrap the word.
strList.add(string.substring(start, lastIndexOfSpace));
i = lastIndexOfSpace + 1; // skip space
} else {
// Should not exceed the width.
if (tempWidth > maxWidth && i != start + 1) {
strList.add(string.substring(start, i - 1));
// Compute from previous char.
--i;
} else {
strList.add(string.substring(start, i));
}
}
// Remove spaces at the beginning of a new line.
while (i < charLength && string.charAt(i) == ' ') {
++i;
}
start = i;
}
}
// Add the last chars.
if (start < charLength) {
strList.add(string.substring(start));
}
return strList;
}
private static String refactorString(final String string) {
// Avoid error when content is "".
if (string.compareTo("") == 0) {
return " ";
}
/*
* If the font of "\n" is "" or "\n", insert " " in front of it. For
* example: "\nabc" -> " \nabc" "\nabc\n\n" -> " \nabc\n \n".
*/
final StringBuilder strBuilder = new StringBuilder(string);
int start = 0;
int index = strBuilder.indexOf("\n");
while (index != -1) {
if (index == 0 || strBuilder.charAt(index - 1) == '\n') {
strBuilder.insert(start, " ");
start = index + 2;
} else {
start = index + 1;
}
if (start > strBuilder.length() || index == strBuilder.length()) {
break;
}
index = strBuilder.indexOf("\n", start);
}
return strBuilder.toString();
}
private static void initNativeObject(final Bitmap bitmap) {
final byte[] pixels = Cocos2dxBitmap.getPixels(bitmap);
@ -471,31 +214,29 @@ public class Cocos2dxBitmap {
}
private static int getFontSizeAccordingHeight(int height) {
Paint paint = new Paint();
TextPaint paint = new TextPaint();
Rect bounds = new Rect();
paint.setTypeface(Typeface.DEFAULT);
int incr_text_size = 1;
int text_size = 1;
boolean found_desired_size = false;
while (!found_desired_size) {
paint.setTextSize(incr_text_size);
paint.setTextSize(text_size);
String text = "SghMNy";
paint.getTextBounds(text, 0, text.length(), bounds);
incr_text_size++;
text_size++;
if (height - bounds.height() <= 2) {
found_desired_size = true;
}
Log.d("font size", "incr size:" + incr_text_size);
}
return incr_text_size;
return text_size;
}
private static String getStringWithEllipsis(String string, float width,
float fontSize) {
float fontSize) {
if (TextUtils.isEmpty(string)) {
return "";
}
@ -507,25 +248,4 @@ public class Cocos2dxBitmap {
return TextUtils.ellipsize(string, paint, width,
TextUtils.TruncateAt.END).toString();
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
private static class TextProperty {
/** The max width of lines. */
private final int mMaxWidth;
/** The height of all lines. */
private final int mTotalHeight;
private final int mHeightPerLine;
private final String[] mLines;
TextProperty(final int maxWidth, final int heightPerLine,
final String[] lines) {
this.mMaxWidth = maxWidth;
this.mHeightPerLine = heightPerLine;
this.mTotalHeight = heightPerLine * lines.length;
this.mLines = lines;
}
}
}