Calculating text width programmatically

There are times when you need to calculate the width of a string of text but do not have access to a text rendering engine.

Letters in a (non-monospaced) font all take up different widths so at minimum we have to build a map of all the available characters we will need. After that there is the complexity of taking into account kerning, this is where certain character pairs are specifically moved closer or further from each other to improve the look of the font.

AVAIL (kerned)
AVAIL (non kerned)

Initially I thought of going down the route of parsing a .ttf font file and using the kerning table and glyph information to build up the set of rules. That was until I looked into it and found there are 4 different types of kerning table just in that format and no web APIs are capable of exposing the information.

So what is a developer to do when the initial solution is accurate but hard to implement ... brute force a solution! Below I go through every printable 32-126 code point ASCII character, rendering it in the DOM and reading its width (with sub-pixel accuracy). Next I do the same for every pair of these, from this I can build a map of the kerning modifier to apply. 8,930 renders later and, voila, I have a set of two maps which can be used to calculate the width of any input.

Letter Width

	
Kerning Modifier

	
To Use
	
		const letterMapSingle = new Map(${paste letter width array from above});
		const letterMapKern = new Map(${paste kerning modifier array from above});

		let letterWidth = 0;

		// split the string up using the spread operator (to handle UTF-8)
		const letterSplit = [...'My String'];

		// go through each letter
		for (const [key, letter] of letterSplit.entries()) {
			// add on the width of this letter to the sum
			letterWidth += letterMapSingle.get(letter) || letterMapSingle.get('_median');

			if (key !== letterSplit.length - 1) {
				// add/remove the kerning modifier of this letter and the next one
				letterWidth += letterMapKern.get(`${letter}${letterSplit[key+1]}`) || 0;
			}
		}

		// now you have your string width for font-size: 100px letters
		// divide by the ratio between 100 and your font-size
		console.log(letterWidth);
	
Limitations
  • Kerning modifiers less than ±0.5px are ignored. This significantly reduces the map size from 1000s to less than 100 normally. This is 0.5px on a font-size of 100px so the actual % error is small but will compound on very long strings.
  • Results vary slightly across browsers.
  • Non 32-126 ASCII characters will crudely use the fallback '_median' width. Usage on anything other than predominantly latin alphabet inputs will not produce an accurate result.
  • If you are dealing with accented letters you may want to pass your input through lodash.deburr (or equivalent) first.
Shortest Longest Letter
Smallest to Largest Kerning
Load Comments...