Generate new Font every time or use pre-generated collection of Fonts?

Viewed 42

I have a method which decides what font size to use for drawing a string. I'm using java.awt.Font.

Font size depends on string length and height, and I use do-while loop to decrease font size until the string fits targetHeight. So in general it looks something like this:

private void decideOnFontSize(String text) {
    int fontSize = 72;
    do {
        font = new Font("Arial", Font.PLAIN, fontSize);
        // Calculating things...
        fontSize -= 2;
    } while (textHeight >= targetHeight);
}

With this approach I need to instantiate a new Font object every time when I need a smaller font size. Method decideOnFontSize is a part of a service in a public API, so it could be called pretty often. At first sight, instantiating new Font looks pretty wasteful in this case.

Another approach is to create a pre-defined collection of Fonts and get them as required. So I could create a utility class like this:

public class FontsUtil {

    private static Map<Integer, Font> fonts = new HashMap<>();

    public static final Integer MAX_FONT_SIZE = 72;
    public static final Integer MIN_FONT_SIZE = 10;

    static {

        String fontName = "Arial";

        for(int fontSize = MAX_FONT_SIZE; fontSize >= MIN_FONT_SIZE; fontSize -= 2) {
            fonts.put(fontSize, new Font(fontName, Font.PLAIN, fontSize));
        }
    }

    public static Font getFontBySize(Integer fontSize) {
        return fonts.get(fontSize);
    }
}

...and then I could get these pre-defined Fonts in do-while loop, so my method will look like this:

private void decideOnFontSize(String text) {
    int fontSize = FontsUtil.MAX_FONT_SIZE;
    do {
        font = FontsUtil.getFontBySize(fontSize);
        // Calculating things...
        fontSize -= 2;
    } while (textHeight >= targetHeight);
}

To my mind, the second approach looks better, because (in theory) it consumes less resources. Am I right, or it doesn't really matter?

UPD: More specifically, I create one or more TextLayout objects using LineBreakMeasurer, because I have two initial params to fit: targetWidth and targetHeight. So my do-while looks like this:

List<TextLayout> lines;
int fontSize = font.getSize();
int textHeight;
do {
    font = FontsUtil.getFontBySize(fontSize);
    g2d.setFont(font);

    lines = splitStringIntoPiecesToFitTargetWidth(text, targetWidth, g2d);
    textHeight = getTextHeight(lines);

    fontSize -= 2;
} while (textHeight >= targetHeight);

*where font is an instance variable

And method splitStringIntoPiecesToFitTargetWidth looks like this:

private List<TextLayout> splitStringIntoPiecesToFitTargetWidth(String string,
int width, Graphics2D g2d) {
    List<TextLayout> lines = new ArrayList<>();

    AttributedString attributedString = new AttributedString(string);
    attributedString.addAttribute(TextAttribute.FONT, font);
    LineBreakMeasurer lineBreakMeasurer =
        new LineBreakMeasurer(attributedString.getIterator(), g2d.getFontRenderContext());

    while (lineBreakMeasurer.getPosition() < string.length()) {
        lines.add(lineBreakMeasurer.nextLayout(width));
    }

    return lines;
}
1 Answers

The latest version of the Font.java source I could find is this one from OpenJDK 8. It defines the following constructor:

public Font(String name, int style, int size) {
    this.name = (name != null) ? name : "Default";
    this.style = (style & ~0x03) == 0 ? style : 0;
    this.size = size;
    this.pointSize = size;
}

To be honest, this doesn't feel like it'll be the bane of your existence performance-wise. I would suggest leaving the version that instantiates new fonts, as it reads easier. If performance does show to be a real issue/concern, you can always go with the other option.

P.S. You should benchmark it, just in case. If the difference is noticeable (I'd put the line at anything >50% faster, but it's up to you), maybe the second option is worth it.

Related