How to fix inconsistent vertical metrics in web fonts

Here's a really specific problem I've run into a handful of times building websites with custom fonts: I set some type on my Windows machine, and everything works as expected. But when I pull up the same page on a Mac (regardless of the browser) the line height is totally different.

My first thought was that something was wrong with my CSS – maybe there's a rogue line-height declaration that gets applied in one place and not the other? But it turned out the problem was actually the font itself: It had different vertical metrics for each platform.

How to fix the problem

The best option is to get whoever produced the font to re-export it with the correct metrics. This is especially true for commercial typefaces, which you're usually not allowed to modify. Failing that, you can generate new font files yourself in one of two ways:

1. FontSquirrel

Upload the file to the FontSquirrel Webfont Generator, switch to Expert mode, check "Auto-Adjust Vertical Metrics", and download the generated fonts. If you're lucky, this will repair the inconsistent metrics and your type will render correctly.

2. Fonttools

If this doesn't work, you can adjust the metrics manually using the command line and a text editor.

Install fonttools and brotli with pip install fonttools brotli. Then cd your way to your project folder and run ttx borked-font.ttf. This will convert the binary ttf into a human-readable XML file called borked-font.ttx.

Open the ttx file in your text editor and look for problems. Specifically, you want to ensure that:

When you're done, run ttx --flavor woff borked-font.ttx to convert it back into a woff file. Set --flavor woff2 to compile straight to woff2, or drop the flag altogether to produce an uncompressed ttf. Load up the new file on your website, and see if you solved the problem.

Background

OpenType fonts are complicated pieces of software. In addition to the actual letterforms (stored as Bézier curves), they contain tables containing the data needed to map these outlines to unicode points and enable things like contextual alternates, kerning pairs, variable fonts, and whatever else you might want to do.

One of the things that's stored in these tables is the font's vertical metrics. This is a set of numbers that define the height of the ascenders, the depth of the descenders, and the recommended linespacing. Rendering engines use these numbers to calculate where the first baseline of a text should fall, what the distance between subsequent lines should be, and how much padding to apply below the last line. They're roughly equivalent to the space above and below the raised letterform on a metal sort.

Drawing of relief letter used in letterpress printing

For historical reasons, vertical metrics are stored in three different places (called hhea, OS/2 typo and OS/2 win), and different rendering engines get their information from different ones. Apple devices generally use hhea, Windows uses either OS/2 typo or OS/2 win, and old versions of MS Office use OS/2 win exclusively. If the numbers in these tables aren't the same, you can end up in a situation where type renders differently in different browsers, design tools, or operating systems.

You can get out of that situation as a user by synching up the numbers yourself, like we did above. First, we set bit 7 in fsSelect to 1 to activate a setting called USE_TYPO_METRICS. This tells browsers on Windows to use the values in OS/2 typo rather than OS/2 win. Then we synched up the values in hhea and OS/2 typo and set OS/2 Win to match the tallest ascender and deppest descender in the font to avoid clipping. Finally we recompiled the font with the new metrics, hopefully solving our issue. There are other approaches to settings vertical metrics, but this is the one recommended by Glyphs and the Google Fonts Team.

If you're a type designer, you can avoid the problem altogether by setting the metrics correctly as you design the typeface, and using automated testing to catch inconsistencies in your build process.

Notes