Text and font rendering on Android
Font size scaling
Text rendering in Android does have a scale ratio. For example, font size = 1,000 is 5 times as big as font size 200, and 10 times as big as font size 100. The abnormal situation we have seen when getting the width and height of text through Paint.getTextBounds()
comes from the integer properties of a Rect. There are 4 int properties of a Rect: left
, right
, top
, and bottom
. left
and top
are initialized by Math.floor
while right
and bottom
are initialized by Math.ceil
. As a result, we lose information on small font sizes.
After analyzing 622 fonts on my computer, I found that only 12 out of 622 fonts have a width difference of more than 20 pixels (the highest being 27 pixels) when scaling textWidth * 5
from a font size of 1,000,000
to Paint.getTextBounds()
with a font size of 5,000,000
.
CREATURE.TTF
FUTRFW.TTF
FUTRFW_0.TTF
LaTribuneCP.ttf
Matchbook.ttf
monof55.ttf
Mr Sheffield.ttf
Sketch.ttf
today.ttf
VCR_OSD_MONO.ttf
VNF-STRANGELOVE-TEXT.TTF
Fit Font Size Finding Algorithm
The algorithm is designed to work with a large base font size. In this example, the base font size is set to 1,000,000 pixels. This large size helps to minimize the impact of rounding errors when calculating the text bounds using Paint.getTextBounds()
. By scaling down from this large size, we can achieve more accurate results for the desired font size that fits within the specified bounds.
float getFitFontSize(
float boundWidth, float boundHeight,
Paint paint, Typeface typeface, String text
){
baseTextSize = 1000000f;
paint.setTextSize(baseTextSize);
paint.setTypeface(typeface);
Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
float sizeW = boundWidth * baseTextSize / (float) (rect.width());
float sizeH = boundHeight * baseTextSize / (float) (rect.height());
return sizeW < sizeH ? sizeW : sizeH;
}
Text Drawing
Text is drawn at the baseline, which is equal to -textBoundsRect.top
(since top
is always negative). Additionally, we should consider textBoundsRect.left
because the width of the text is calculated by right - left
, and for some fonts and font sizes, left
may not be equal to zero.
So, the drawing text should be:
canvas.drawText(
text,
leftMost - textBoundsRect.left,
topMost - textBoundsRect.top, paint
);
Shadow
paint.setShadowLayer(blur, offsetX, offsetY, color);
When shadow is turned on, we need to adjust the font size finding algorithm and the drawing method slightly.
The new algorithm should be:
float getFitFontSize(
float boundWidth, float boundHeight,
float offsetX, float offsetY,
Paint paint, Typeface typeface, String text
){
boundWidth -= offsetX;
boundHeight -= offsetY;
baseTextSize = 1000000f;
paint.setTextSize(baseTextSize);
paint.setTypeface(typeface);
Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
float sizeW = boundWidth * baseTextSize / (float) (rect.width());
float sizeH = boundHeight * baseTextSize / (float) (rect.height());
return sizeW < sizeH ? sizeW : sizeH;
}
And the text drawing should be:
paint.setShadowLayer(blur, offsetX, offsetY, colour);
float dx = offsetX < 0 ? -offsetX : 0;
float dy = offsetY < 0 ? -offsetY : 0;
canvas.drawText(
text,
leftMost - textBoundsRect.left + dx,
topMost - rect.top + dy, paint
);
Note: The above code is not correct in case of blur > 1
.