Use Javascript to get Maximum font size in canvas

Challenge

As canvas’ measureText doesn’t currently support measuring height (ascent + descent) we need to do a little DOM trick to get the line-height.

As the actual height of a font – or typeface – does not necessarily (rarely) correspond with the font-size, we need a more accurate way of measuring it.

Solution

Fiddle demo

The result will be a vertical aligned text which always fit the canvas in width.

snap

This solution will automatically get the optimal size for the font.

The first function is to wrap the measureText to support height. If the new implementation of the text metrics isn’t available then use DOM:

function textMetrics(ctx, txt) {

    var tm = ctx.measureText(txt),
        w = tm.width,
        h, el;  // height, div element
    
    if (typeof tm.fontBoundingBoxAscent === "undefined") {
    
        // create a div element and get height from that
        el = document.createElement('div');
        el.style.cssText = "position:fixed;font:" + ctx.font +
                           ";padding:0;margin:0;left:-9999px;top:-9999px";
        el.innerHTML = txt;

        document.body.appendChild(el); 
        h = parseInt(getComputedStyle(el).getPropertyValue('height'), 10);
        document.body.removeChild(el);

    } 
    else {
        // in the future ...
        h = tm.fontBoundingBoxAscent + tm.fontBoundingBoxDescent;
    }

    return [w, h];
}

Now we can loop to find the optimal size (here not very optimized, but works for the purpose – and I wrote this just now so there might be bugs in there, one being if text is just a single char that doesn’t exceed width before height).

This function takes minimum two arguments, context and the text, the others are optional such as font name (name only), tolerance [0.0, 1.0] (default 0.02 or 2%) and style (ie. bold, italic etc.):

function getOptimalSize(ctx, txt, fontName, tolerance, tolerance, style) {

    tolerance = (tolerance === undefined) ? 0.02 : tolerance;
    fontName = (fontName === undefined) ? 'sans-serif' : fontName;
    style = (style === undefined) ? '' : style + ' ';
    
    var w = ctx.canvas.width,
        h = ctx.canvas.height,
        current = h,
        i = 0,
        max = 100,
        tm,
        wl = w - w * tolerance * 0.5,
        wu = w + w * tolerance * 0.5,
        hl = h - h * tolerance * 0.5,
        hu = h + h * tolerance * 0.5;
    
    for(; i < max; i++) {

        ctx.font = style + current + 'px ' + fontName;
        tm = textMetrics(ctx, txt);
        
        if ((tm[0] >= wl && tm[0] <= wu)) {
            return tm;
        }
        
        if (tm[1] > current) {
            current *= (1 - tolerance);
        } else {
            current *= (1 + tolerance);
        }            
    }
    return [-1, -1];
}

Leave a Comment