· Typography · 7 min read
Vertical Metrics Explained: hhea, typo, win, and USE_TYPO_METRICS
Why the same font can get a different line height on macOS and Windows: the three sets of vertical metrics, the USE_TYPO_METRICS flag, and what you can do in CSS.
You set a font and line-height: normal, and the page looks right on your Mac. On a Windows machine the same paragraph is taller, or a button label sits too low. Nothing in your CSS changed. The difference comes from inside the font file: it stores its vertical measurements more than once, and different systems read different copies.
This guide explains the three sets of vertical metrics, the flag that is supposed to settle which one Windows uses, and what you can do about it in CSS when you cannot change the font.
The basics: ascender, descender, line gap
Font outlines are drawn on a grid of font units. The size of that grid is the font’s units per em, often 1000 or 2048. One em is the font size you set in CSS.
Three numbers describe a line of text:
- Ascender: how far above the baseline the line reaches.
- Descender: how far below the baseline it reaches.
- Line gap: extra space added between lines.
Together they give the font’s default line height. With 1000 units per em, an ascender of 950, a descender of 250 below the baseline, and no line gap add up to 1200 units, so the default line height is 1.2 times the font size. This is what browsers use when line-height is normal.
Three copies of the same numbers
For historical reasons, a font stores these values in three places. The Google Fonts guide to vertical metrics calls them the hhea, typo, and win metrics:
| Set | Where it is stored | Fields |
|---|---|---|
| hhea | hhea table | ascender, descender, lineGap |
| typo | OS/2 table | sTypoAscender, sTypoDescender, sTypoLineGap |
| win | OS/2 table | usWinAscent, usWinDescent (no line gap) |
One detail trips people up: usWinDescent counts downward from the baseline, so it is usually a positive number, while the hhea and typo descenders are usually negative. The OpenType specification points this out in its description of the field.
Who reads which set
According to the Google Fonts guide:
- macOS uses the hhea metrics.
- Microsoft applications use the win metrics, unless the font sets the
USE_TYPO_METRICSflag, in which case they use the typo metrics.
The win values have a second job on Windows. The OpenType specification explains that Windows GDI clips any part of a glyph that reaches above usWinAscent or below usWinDescent. If those values are too small, tall accents or deep descenders get cut off. If they are very large, lines spread apart in applications that use them for spacing.
The USE_TYPO_METRICS flag
USE_TYPO_METRICS is bit 7 of the fsSelection field in the OS/2 table. The specification describes what it means: when it is set, applications are strongly recommended to use the typo values for default line spacing. That lets the win values be large enough to prevent clipping without also stretching the line height.
The specification calls the typo values the portable choice for layout, and for variable fonts it says the flag should be set and the hhea values should match the typo values.
Why line heights differ between macOS and Windows
Put the two rules together. On macOS the line height comes from hhea. On Windows it comes from win, or from typo if the flag is set. If those sets disagree, so does the line height.
A hypothetical font with 1000 units per em shows how easily this happens:
hhea: ascender 950, descender -250, line gap 0 → 1.2 em on macOS
win: ascent 1050, descent 350 → 1.4 em on Windows (flag off)
typo: ascender 950, descender -250, line gap 0 → 1.2 em on Windows (flag on)With the flag off, Windows uses the taller win values and every line is about 17% taller than on a Mac. With the flag on and typo matching hhea, both systems agree.
That is why the Google Fonts guide asks fonts in its library to have identical typo and hhea values, a line gap of 0, win values that cover the tallest and deepest glyphs in the family, and USE_TYPO_METRICS turned on. It also asks that every style in a family share the same values, so switching to bold does not change the line height.
Check a font
Open the font in the font viewer and expand its Vertical metrics panel. It shows:
- the hhea, typo, and win values side by side, with the line height each one gives;
- whether
USE_TYPO_METRICSis on; - which set macOS and Windows will use, and whether their line heights differ;
- a drawing of the ascender, cap height, x-height, baseline, and descender lines, for whichever set you choose.
If the panel says the two systems give the same line height, there is nothing to fix. If not, use one of the approaches below.
What you can do in CSS
If you can change the font file, fixing it once is the cleanest solution; the next section covers that. If you cannot, or the license does not allow it, CSS still gives you two good options.
1. Set an explicit line height
A unitless line-height makes every line the same height in every browser, whatever the font’s metrics say:
body {
line-height: 1.5;
}This fixes the spacing between lines. It does not fully fix where the text sits inside each line: the browser places the glyphs using the font’s ascent and descent, so if those differ between systems, text can still sit a pixel or two higher or lower. You notice this most in buttons and badges, where text is meant to be centered.
2. Override the metrics in @font-face
The ascent-override, descent-override, and line-gap-override descriptors replace the font’s own values with numbers you choose, as a percentage of the font size. Every browser that supports them then lays the font out the same way, whichever set the system would have picked:
@font-face {
font-family: 'Brand Sans';
src: url('/fonts/brand-sans-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
/* hhea values of a hypothetical font with 1000 units per em */
ascent-override: 95%; /* ascender 950 / 1000 */
descent-override: 25%; /* descender 250 / 1000 */
line-gap-override: 0%;
}Divide each value from the font viewer by the units per em, and use the descender as a positive number. Using the hhea values is a sensible default: released versions of Safari do not support these three descriptors, and Safari on macOS reads the hhea values anyway, so it ends up with the same numbers.
These are the same descriptors used to make fallback fonts match a web font. The font fallback generator calculates them for system fallbacks such as Arial from your font’s metrics, using the typo values when USE_TYPO_METRICS is on, and shows how much the text moves with and without them. How to reduce layout shift from web fonts explains the method.
Fix the font itself
When the font’s license allows changes, fix the metrics at the source rather than in every stylesheet.
FontPivot’s converter has a Fix vertical metrics option, off by default, in its advanced options on the home page and in the webfont generator. It applies the Google Fonts rules described above:
- the hhea and typo values become the same, with a line gap of 0;
- the win values are set to cover every glyph in the font, so nothing is clipped on Windows;
USE_TYPO_METRICSis turned on, so Windows uses the typo values.
It keeps the line height the font has on macOS today, by folding any line gap into the ascender and descender. When you turn it on, it lists each font’s values before and after, and the line height on macOS and on Windows. For example, the Arial that ships with macOS has a line height of 1.15 there but 1.12 on Windows, because its USE_TYPO_METRICS flag is off and its win values leave out the line gap; with the fix, both become 1.15.
If you edit fonts yourself, the Google Fonts vertical metrics guide also explains how to choose the values. Either way, check the result in the font viewer. Packing a font into WOFF or WOFF2, and unpacking it again into its original format, keeps these tables as they are.
Summary
- Fonts store vertical metrics three times: hhea, typo, and win.
- macOS uses hhea; Microsoft applications use win, or typo when
USE_TYPO_METRICSis set. - If those disagree,
line-height: normaldiffers between systems. - Check the Vertical metrics panel in the font viewer.
- If the license allows, fix the font with the converter’s Fix vertical metrics option.
- In CSS, set an explicit
line-height, and useascent-override,descent-override, andline-gap-overrideto make the metrics the same everywhere.
Further reading
- Google Fonts: Vertical metrics
- Microsoft: OS/2 table specification and hhea table specification
- W3C: Font metrics override descriptors in CSS Fonts Level 4