How can I get the height of a given string's descender? - android

How can I get the height of a given string's descender?
For instance,
abc should return 0.
abcl should return 0.
abcp should return distance from descnder line to baseline.
abclp should return distance from descnder line to baseline.
The best I could came out so far is
private int getDecender(String string, Paint paint) {
// Append "l", to ensure there is Ascender
string = string + "l";
final String stringWithoutDecender = "l";
final Rect bounds = new Rect();
final Rect boundsForStringWithoutDecender = new Rect();
paint.getTextBounds(string, 0, string.length(), bounds);
paint.getTextBounds(stringWithoutDecender, 0, stringWithoutDecender.length(), boundsForStringWithoutDecender);
return bounds.height() - boundsForStringWithoutDecender.height();
}
However, my code smell is that they are not good enough. Is there any better and faster way?

Actually I was looking for the same functionality. It turns out there is much simpler way,
you do not even need separate function for that.
If you just call getTextBounds() on a given string, the returned bounding box will already have that information.
For example:
paint.getTextBounds(exampleString1 , 0, exampleString1.length(), bounds);
if (bounds.bottom > 0) Log.i("Test", "String HAS descender");
else Log.i("Test", "String DOES NOT HAVE descender");
Simply saying bounds.top tells you the ascent of the string (it has negative value as Y axis 0 point is at the baseline of the string) and bounds.bottom tells you the descent of the string (which can be 0 or positive value for strings having descent).

You should have a look at Paint.FontMetrics. The descent member will give you "The recommended distance below the baseline for singled spaced text.".

Related

canvas drawtext with multiline

I am developing a image commenting application. I draw text in canvas with canvas.drawText(text, x, y, imgPaint);
This appears in a single line. I need to break the line to multiline when the text crosses the canvas width
Thanks in advance
You need to use StaticLayout:
TextPaint mTextPaint=new TextPaint();
StaticLayout mTextLayout = new StaticLayout("my text\nNext line is very long text that does not definitely fit in a single line on an android device. This will show you how!", 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 = 100;
textY = 100;
canvas.translate(textX, textY);
mTextLayout.draw(canvas);
canvas.restore();
You need to split the line and draw each fragment separately with an increasing y based on font-height.
For example:
var lines = text.split("\n"),
x = 100, y = 100, fHeight = 16, // get x, y and proper font or line height here
i = 0, line;
while(line = lines[i++]) {
canvas.drawText(line, x, y, imgPaint);
y += fHeight;
}
Well it's quite late to add another answer but if someone doesn't want to use StaticLayout then they can try my logic for multiLine text
Note : This code is used in onSizeChanged() method of View and textArray is a class variable that store each line
//This array will store all the words contained in input string
val wordList = ArrayList<String>()
//Temporary variable to store char or string
var temp = ""
it.trim().forEachIndexed { index, letter ->
//Adding each letter to temp
temp += letter
//If letter is whiteSpace or last char then add it to wordList.
//For example : Let input be "This is a Info text"
// since there is no whiteSpace after that last 't' then the last word
// will not be added to wordList there for checking for last letter is required
//NOTE: the whiteSpace is also included in that word
if (letter.isWhitespace() || index == it.length -1 ) {
wordList.add(temp)
//Resetting temp
temp = ""
}
}
wordList.forEachIndexed { index, word ->
//Measuring temp + word to check if their width in pixel is more than or equal to
// the view's width + 60px(this is used so that word there is some space after each line. It can be changed)
if (textPaint.measureText(temp + word) >= w - 60) {
textArray.add(temp)
//If adding last word to temp surpasses the required width then add the last word
// separately since the loop will be terminated after that
if (index == wordList.size - 1){
textArray.add(word)
return#forEachIndexed
}
//Resetting temp
temp = ""
} else if (index == wordList.size - 1) {
//If adding last word to temp doesn't surpasses the required width the add that
// line to list
textArray.add(temp + word)
return#forEachIndexed
}
//Adding word to temp
temp += word
}
Then in onDraw() method
textArray.forEachIndexed { index, singleLine ->
//x is set to 16f so that there is some space before first word
//y changes with each line i.e 1st line will be drawn at y = 60f, 2nd at 120f and so on
it.drawText(singleLine, 16f, (index + 1) * 60f, textPaint)
}

getTextWidths method is giving wrong result

There is a problem with some Paint class methods. I should get the width of spanned string given to the TextView. Let's say I'm measuring text at first row. When I measure it programmatically it gives 286, but the width of the screen is 240? Maybe someone had such a problem?Any help will be appreciated...
The code for calculating width of spanned text:
private int calcWidthSize(CharSequence spannedString, int currentSize) {
int res = 0;
Paint paint = new Paint();
float[] a= new float[444];
paint.setTextSize(currentSize);
paint.getTextWidths(spannedString,0,spannedString.length(),a);
for (int num=0;num<a.length;num++){
res+=(int)a[num];
}
return res;
}
Solved.The width of display is in pixels format and equals to 240.Text measure gives us result in dp format

How to get ascender/descender and x height for a given font

I need to get a ascender/descender and x-height..
By using following code I can find the descender and the total height:
descender_height = paint.descent();
total_height = descender_height - paint.ascent();
//ascender = ?; is this always equal to descender height?
//x_height = ?; total_height - 2*descender_height ?
Thanks
I would think the ascender and descender height would typically be the same, but I wouldn't depend on it for every font. I don't really see a direct way to get to the x-height, but a trick you could use would be something like the below. Also, for the total height, are you talking about the absolute distance from the highest ascender to the lowest descender? I've also included something for that below. I haven't tested these myself, but it should work (but let me know if I'm misinterpreting something you've said):
// Assuming TextPaint/Paint tp;
Rect bounds;
// this will just retrieve the bounding rect for 'x'
tp.getTextBounds("x", 0, 1, bounds);
int xHeight = bounds.height();
Paint.FontMetrics metrics = tp.getFontMetrics();
int totalHeight = metrics.top - metrics.bottom;
This is what worked for me:
Paint.FontMetrics fm = paint.getFontMetrics();
int totalHeight = (int)(fm.bottom - fm.top + .5f);

Measuring text height to be drawn on Canvas ( Android )

Any straight forward way to measure the height of text?
The way I am doing it now is by using Paint's measureText() to get the width, then by trial and error finding a value to get an approximate height. I've also been messing around with FontMetrics, but all these seem like approximate methods that suck.
I am trying to scale things for different resolutions. I can do it, but I end up with incredibly verbose code with lots of calculations to determine relative sizes. I hate it! There has to be a better way.
There are different ways to measure the height depending on what you need.
#1 getTextBounds
If you are doing something like precisely centering a small amount of fixed text, you probably want getTextBounds. You can get the bounding rectangle like this
Rect bounds = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), bounds);
int height = bounds.height();
As you can see for the following images, different strings will give different heights (shown in red).
These differing heights could be a disadvantage in some situations when you just need a constant height no matter what the text is. See the next section.
#2 Paint.FontMetrics
You can calculate the hight of the font from the font metrics. The height is always the same because it is obtained from the font, not any particular text string.
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
float height = fm.descent - fm.ascent;
The baseline is the line that the text sits on. The descent is generally the furthest a character will go below the line and the ascent is generally the furthest a character will go above the line. To get the height you have to subtract ascent because it is a negative value. (The baseline is y=0 and y descreases up the screen.)
Look at the following image. The heights for both of the strings are 234.375.
If you want the line height rather than just the text height, you could do the following:
float height = fm.bottom - fm.top + fm.leading; // 265.4297
These are the bottom and top of the line. The leading (interline spacing) is usually zero, but you should add it anyway.
The images above come from this project. You can play around with it to see how Font Metrics work.
#3 StaticLayout
For measuring the height of multi-line text you should use a StaticLayout. I talked about it in some detail in this answer, but the basic way to get this height is like this:
String text = "This is some text. This is some text. This is some text. This is some text. This is some text. This is some text.";
TextPaint myTextPaint = new TextPaint();
myTextPaint.setAntiAlias(true);
myTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
myTextPaint.setColor(0xFF000000);
int width = 200;
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
float spacingMultiplier = 1;
float spacingAddition = 0;
boolean includePadding = false;
StaticLayout myStaticLayout = new StaticLayout(text, myTextPaint, width, alignment, spacingMultiplier, spacingAddition, includePadding);
float height = myStaticLayout.getHeight();
What about paint.getTextBounds() (object method)
#bramp's answer is correct - partially, in that it does not mention that the calculated boundaries will be the minimum rectangle that contains the text fully with implicit start coordinates of 0, 0.
This means, that the height of, for example "Py" will be different from the height of "py" or "hi" or "oi" or "aw" because pixel-wise they require different heights.
This by no means is an equivalent to FontMetrics in classic java.
While width of a text is not much of a pain, height is.
In particular, if you need to vertically center-align the drawn text, try getting the boundaries of the text "a" (without quotes), instead of using the text you intend to draw.
Works for me...
Here's what I mean:
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);
Rect bounds = new Rect();
paint.getTextBounds("a", 0, 1, bounds);
buffer.drawText(this.myText, canvasWidth >> 1, (canvasHeight + bounds.height()) >> 1, paint);
// remember x >> 1 is equivalent to x / 2, but works much much faster
Vertically center aligning the text means vertically center align the bounding rectangle - which is different for different texts (caps, long letters etc). But what we actually want to do is to also align the baselines of rendered texts, such that they did not appear elevated or grooved. So, as long as we know the center of the smallest letter ("a" for example) we then can reuse its alignment for the rest of the texts. This will center align all the texts as well as baseline-align them.
The height is the text size you have set on the Paint variable.
Another way to find out the height is
mPaint.getTextSize();
You could use the android.text.StaticLayout class to specify the bounds required and then call getHeight(). You can draw the text (contained in the layout) by calling its draw(Canvas) method.
You can simply get the text size for a Paint object using getTextSize() method.
For example:
Paint mTextPaint = new Paint (Paint.ANTI_ALIAS_FLAG);
//use densityMultiplier to take into account different pixel densities
final float densityMultiplier = getContext().getResources()
.getDisplayMetrics().density;
mTextPaint.setTextSize(24.0f*densityMultiplier);
//...
float size = mTextPaint.getTextSize();
You must use Rect.width() and Rect.Height() which returned from getTextBounds() instead. That works for me.
If anyone still has problem, this is my code.
I have a custom view which is square (width = height) and I want to assign a character to it. onDraw() shows how to get height of character, although I'm not using it. Character will be displayed in the middle of view.
public class SideBarPointer extends View {
private static final String TAG = "SideBarPointer";
private Context context;
private String label = "";
private int width;
private int height;
public SideBarPointer(Context context) {
super(context);
this.context = context;
init();
}
public SideBarPointer(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public SideBarPointer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
init();
}
private void init() {
// setBackgroundColor(0x64FF0000);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
height = this.getMeasuredHeight();
width = this.getMeasuredWidth();
setMeasuredDimension(width, width);
}
protected void onDraw(Canvas canvas) {
float mDensity = context.getResources().getDisplayMetrics().density;
float mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
Paint previewPaint = new Paint();
previewPaint.setColor(0x0C2727);
previewPaint.setAlpha(200);
previewPaint.setAntiAlias(true);
Paint previewTextPaint = new Paint();
previewTextPaint.setColor(Color.WHITE);
previewTextPaint.setAntiAlias(true);
previewTextPaint.setTextSize(90 * mScaledDensity);
previewTextPaint.setShadowLayer(5, 1, 2, Color.argb(255, 87, 87, 87));
float previewTextWidth = previewTextPaint.measureText(label);
// float previewTextHeight = previewTextPaint.descent() - previewTextPaint.ascent();
RectF previewRect = new RectF(0, 0, width, width);
canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
canvas.drawText(label, (width - previewTextWidth)/2, previewRect.top - previewTextPaint.ascent(), previewTextPaint);
super.onDraw(canvas);
}
public void setLabel(String label) {
this.label = label;
Log.e(TAG, "Label: " + label);
this.invalidate();
}
}

How to get string width on Android?

I would like to get height too if possible.
You can use the getTextBounds(String text, int start, int end, Rect bounds) method of a Paint object. You can either use the paint object supplied by a TextView or build one yourself with your desired text appearance.
Using a Textview you Can do the following:
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text, 0, text.length(), bounds);
int height = bounds.height();
int width = bounds.width();
If you just need the width you can use:
float width = paint.measureText(string);
http://developer.android.com/reference/android/graphics/Paint.html#measureText(java.lang.String)
There are two different width measures for a text. One is the number of pixels which has been drawn in the width, the other is the number of 'pixels' the cursor should be advanced after drawing the text.
paint.measureText and paint.getTextWidths returns the number of pixels (in float) which the cursor should be advanced after drawing the given string. For the number of pixels painted use paint.getTextBounds as mentioned in other answer. I believe this is called the 'Advance' of the font.
For some fonts these two measurements differ (alot), for instance the font Black Chancery have letters which extend past the other letters (overlapping) - see the capital 'L'. Use paint.getTextBounds as mentioned in other answer to get pixels painted.
I have measured width in this way:
String str ="Hiren Patel";
Paint paint = new Paint();
paint.setTextSize(20);
Typeface typeface = Typeface.createFromAsset(getAssets(), "Helvetica.ttf");
paint.setTypeface(typeface);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
Rect result = new Rect();
paint.getTextBounds(str, 0, str.length(), result);
Log.i("Text dimensions", "Width: "+result.width());
This would help you.
Most likely you want to know the painted dimensions for a given string of text with a given font (i.e. a particular Typeface such as the “sans-serif” font family with a BOLD_ITALIC style, and particular size in sp or px).
Rather than inflating a full-blown TextView, you can go lower level and work with a Paint object directly for single-line text, for example:
// Maybe you want to construct a (possibly static) member for repeated computations
Paint paint = new Paint();
// You can load a font family from an asset, and then pick a specific style:
//Typeface plain = Typeface.createFromAsset(assetManager, pathToFont);
//Typeface bold = Typeface.create(plain, Typeface.DEFAULT_BOLD);
// Or just reference a system font:
paint.setTypeface(Typeface.create("sans-serif",Typeface.BOLD));
// Don't forget to specify your target font size. You can load from a resource:
//float scaledSizeInPixels = context.getResources().getDimensionPixelSize(R.dimen.mediumFontSize);
// Or just compute it fully in code:
int spSize = 18;
float scaledSizeInPixels = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
spSize,
context.getResources().getDisplayMetrics());
paint.setTextSize(scaledSizeInPixels);
// Now compute!
Rect bounds = new Rect();
String myString = "Some string to measure";
paint.getTextBounds(myString, 0, myString.length(), bounds);
Log.d(TAG, "width: " + bounds.width() + " height: " + bounds.height());


For multi-line or spanned text (SpannedString), consider using a StaticLayout, in which you provide the width and derive the height. For 
a very elaborate answer on measuring and drawing text to a canvas in a custom view doing that, see: https://stackoverflow.com/a/41779935/954643
Also worth noting #arberg's reply below about the pixels painted vs the advance width ("number of pixels (in float) which the cursor should be advanced after drawing the given string"), in case you need to deal with that.
I'd like to share a better way (more versatile then the current accepted answer) of getting the exact width of a drawn text (String) with the use of static class StaticLayout:
StaticLayout.getDesiredWidth(text, textPaint))
this method is more accurate than textView.getTextBounds(), since you can calculate width of a single line in a multiline TextView, or you might not use TextView to begin with (for example in a custom View implementation).
This way is similar to textPaint.measureText(text), however it seems to be more accurate in rare cases.
simplay i tack max charcter in the line and defieded it with max space and create new line
v_y = v_y + 30;
String tx = "مبلغ وقدرة : "+ AmountChar+" لا غير";
myPaint.setTextAlign(Paint.Align.RIGHT);
int pxx = 400;
int pxy = v_y ;
int word_no = 1;
int word_lng = 0;
int max_word_lng = 45;
int new_line = 0;
int txt_lng = tx.length();
int words_lng =0;
String word_in_line = "" ;
for (String line : tx.split(" "))
{
word_lng = line.length() ;
words_lng += line.length() + 1;
if (word_no == 1 )
{word_in_line = line;
word_no += 1;
}
else
{ word_in_line += " " + line;
word_no += 1;
}
if (word_in_line.length() >= max_word_lng)
{
canvas.drawText(word_in_line, pxx, pxy, myPaint);
new_line += 1;
pxy = pxy + 30;
word_no = 1;
word_in_line = "";
}
if (txt_lng <= words_lng )
{ canvas.drawText(word_in_line, pxx, pxy, myPaint); }
}
v_y = pxy;

Categories

Resources