Autosizing font to make text fit in desired bounds

Hi! :slightly_smiling_face:

So another programmer and I have been working on a project that we’ve recently begun to port from Phaser 2 CE to Phaser 3 (primarily for the rtl functionality in Phaser 3). The text displayed in the application is pulled from a csv. To make sure that the text never exceeds the desired bounds if the content changes and the text gets longer or shorter, we’ve been using the following code to change the font size until the text fits within the specified bounds (adapted from this post: http://www.html5gamedevs.com/topic/15251-phasertext-auto-size-font-for-dynamic-text/)

TextAdvanced.prototype.autoSizeFont = function(w,h)
{
    if (typeof this.defaultFontSize === "undefined")
    {
        this.defaultFontSize = this.fontSize;
    }
    this.fontSize = this.defaultFontSize;

    var width = w || this.textBounds.width;
    var height = h || this.textBounds.height;

    if (width > 0 && height > 0)
    {
        var size = this.defaultFontSize;
        while ((this.width > width || this.height > height) && this.fontSize > 4)
        {
            --size;
            this.fontSize = size;
        }
    }
};

Which worked great. So as we are now porting to Phaser 3, I attempted to port the above code and ended up with the following:

Text.TextAdvanced.prototype.autoSizeFont = function(w, h)
{
    if (typeof this.defaultFontSize === "undefined")
    {
        this.defaultFontSize = parseInt(this.style.fontSize.replace("px", ""));
    }
    
    var width = w || this.textBounds.width;
    var height = h || this.textBounds.height;    
    
    var size = this.defaultFontSize;
    if (width > 0 && height > 0)
    {        
        while ((this.width > width || this.height > height) && size > 4)
        {
           --size;           
           this.setFontSize(size);
        }                
    }
};

which I found was super slow, taking multiple seconds just to autosize a single Text object. My next thought was to use the Text object’s context and measureText to do the calculations, and so I ended up with this:

Text.TextAdvanced.prototype.autoSizeFont = function(w, h)
{
    if (typeof this.defaultFontSize === "undefined")
    {
        this.defaultFontSize = parseInt(this.style.fontSize.replace("px", ""));
    }
    
    var width = w || this.textBounds.width;
    var height = h || this.textBounds.height;    
    
    var size = this.defaultFontSize;
    var family = this.style.fontFamily;

    if (width > 0 && height > 0)
    {        
        var metrics = this.context.measureText(this.text);
        while ((metrics.width > width) && size > 4)
        {
           --size;           
           this.context.font = size.toString() + "px " + family;
           metrics = this.context.measureText(this.text);
        }
        this.setFontSize(size);
    }
};

Which seems to work for single-line text, and is relatively quick. Unfortunately, it does not take word wrapping into account… My current train of thought is to use Phaser’s static GetTextSize and MeasureText functions to somehow calculate the text bounds (accounting for word wrapping) without actually rebuilding the Text object.

Anyway, I was just wondering if anyone has a better way to do this in Phaser 3, or any suggestions as to how to fix my current method of autosizing. Am I over-complicating this or missing something obvious here?

1 Like

Here’s what I ended up coming up with. I might make this more of a binary search algorithm at some point to improve efficiency :slight_smile: .

Text.TextAdvanced.prototype.autoSizeFont = function(w, h)
{
    var myMetrics = this.getTextMetrics();    
    if (typeof this.defaultFontSize === "undefined")
    {
        this.defaultFontSize = myMetrics.fontSize;
    }
    
    var size = this.defaultFontSize;    
    var width = w || this.textBounds.width;
    var height = h || this.textBounds.height;
    var family = this.style.fontFamily;

    if (width > 0 && height > 0)
    {
        myMetrics.fontSize = size;
        this.context.font = size.toString() + "px " + family;    

        var text = this.text;
        var lines = this.basicWordWrap(text, this.context, width).split('\n');            
        var dimensions = Text.GetTextSize(this, myMetrics, lines);
		
        while ((dimensions.width > width || dimensions.height > height) && size > 4)
        {
            --size;
            
            this.context.font = size.toString() + "px " + family;            
            myMetrics.fontSize = size;
            
            lines = this.basicWordWrap(text, this.context, width).split('\n');
            dimensions = Text.GetTextSize(this, myMetrics, lines);  
        }
        this.text = this.basicWordWrap(text, this.context, width);
        this.style.setFontSize(size);       
    }
};
1 Like

I also wrote something of the same type, using arithmetic with context.measureText() to find the font size. And just like the above, it was a little chunky. But in GetTextSize.js in gameobjects/text, they already do all of that arithmetic and expose the height via Phaser.GameObjects.Text.height. So if anyone needs dynamic resizing to keep from exceeding a fixed height, you could do something like:

    let fontSize=40
    let textToDisplayWithFixedHeight=scene.add.text(0,0,"text with unknown wordcount",myFavoriteStyle)
    while(textToDisplayWithFixedHeight.height>fixedHeightIWantEnforced)){
        fontSize=Math.floor(fontSize*.9)
        textToDisplayWithFixedHeight.setFontSize(fontSize)
    }
1 Like

Just wanted to say, thanks, the bottom solution worked perfect for my needs.

I was able to drop it in with one minor edit:

Phaser.GameObjects.Text.prototype.autoSizeFont = function(w, h)