Text and font rendering on Android

Some notes about text rendering of Android

Font size scaling

Text rendering in Android does have 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 get width and height of text through Paint.getTextBounds() comes from the integer. There are 4 int properties of a Rect: left, right, top, bottom. left and top are initiated by Math.floor while right and bottom are initiated by Math.ceil. As the result, we lost information on small font size.

After doing stats with 622 fonts on my computer, I experience that only 12/622 fonts have the width value different more than 20 pixels (the highest is 27 pixels) on scaling textWidth * 5 of font size 1,000,000 to Paint.getTextBounds() of font size 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 created based on the big font size. Currently, I set font size 1,000,000 pixels.

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 the -textBoundsRect.top (top is always negative). Besides, we should take care of the textBoundsRect.left because the width of text is calculated by right — left, and with some font and font size, 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 change a little bit about the finding font size algorithm and drawing.

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

The above code is not correct in case of blur > 1.