Technote

Performance optimization Advanced

Series Korean web typography Part 7 of 8

Metric overrides: stopping the jump when the font lands

Text jumps when a webfont lands not because it was late, but because the fallback had different widths and heights. Match the two and the swap leaves the layout untouched.

Attach a webfont with font-display: swap and the screen will change once. Usually it does not stop there: the whole layout moves — a heading folds from three lines to two, and everything beneath it is pulled upward.

Diagnosing this as “the font was slow” leads nowhere, because however fast the font arrives, the difference at the moment of the swap is unchanged. The cause is not speed. It is that the two typefaces have different metrics.

Different in what way

In two ways: glyph width and vertical metrics.

Different widths mean a different number of characters per line. If the fallback runs slightly wider, the same paragraph occupies one more line — and when the webfont lands and that line disappears, everything below rises.

Different vertical metrics (ascent, descent, line gap) mean the glyphs sit at a different height even at the same line-height. The line count can match and the text will still nudge up or down.

The same font-display: swap, with a very different outcome

Build a fallback family of your own

The trick is not to use the system font directly, but to wrap it in a new @font-face family whose metrics you override. CSS gives you four properties for this.

@font-face {
  font-family: 'Pretendard Fallback';   /* a name we invent */
  src: local('Malgun Gothic');
  size-adjust:        88.19%;   /* match width and apparent size */
  ascent-override:    95%;      /* height above the baseline */
  descent-override:   25%;      /* height below it */
  line-gap-override:  0%;
}

body {
  font-family: 'Pretendard Variable', 'Pretendard Fallback', sans-serif;
}

size-adjust does the heavy lifting. It scales the whole glyph set proportionally, so widths and apparent size are matched together. Matching widths means matching line breaks, and matching line counts means nothing below moves.

The other three align where the glyphs sit inside the line box. Every font carries its own ascent and descent values; normalising them means the baseline lands in the same place at a given line-height.

The numbers come from measurement

Do not copy the numbers above. Override values are specific to a pair of typefaces. Change the webfont and they change; and the fallback itself differs between operating systems.

Deriving them is not difficult. Render the same sentence in both faces, measure the widths, and use the ratio as your size-adjust. The canvas text-measurement API is enough.

const measure = (family) => {
  const c = document.createElement('canvas').getContext('2d');
  c.font = `100px ${family}`;
  return c.measureText('A sentence with Hangul and Latin ABC 123').width;
};

// the ratio that fits the fallback to the webfont
const adjust = measure('Pretendard Variable') / measure('Malgun Gothic');
console.log((adjust * 100).toFixed(2) + '%');

Use characters that actually appear in your body copy in the test string. Measuring Hangul alone can drift on real paragraphs that mix in Latin letters and numerals.

Residual error, and how to check

This technique does not reduce the error to zero. size-adjust scales everything by one ratio, so per-glyph width differences remain. In Korean paragraphs using word-break: keep-all, a word sitting right on the edge of a line can still tip the line count either way.

It is nonetheless a different order of problem from the unadjusted case. Verify with tools rather than eyes: record a page load in the browser’s performance panel and check whether layout-shift entries remain — and if they do, which element produced them.

Four steps — only step 2 is re-measured per pair of fonts

Layout shift and the wider performance metrics live in the Performance archive, and recording this kind of measurement as a before-and-after comparison is exactly what goes into the report in our optimization program.

Next part

With the letters arriving predictably, what is left is how to arrange them. The final part covers measure and vertical rhythm — the setting that gets long articles read to the end.

More on this topic

All technotes

Performance optimization Practical

Who pays for the milliseconds a tag adds?

A tracking script does not stop at downloading. While it is parsed and executed the main thread is busy, and a busy page ignores fingers. That cost is…

Marketers 5 min read

Performance optimization Practical

The object cache: what Redis actually removes

Repeated identical lookups disappear. One slow query does not. Miss that distinction and you will wire up Redis and then ask why nothing got faster.

Developers 7 min read

Performance optimization Practical

This is how a landing page gets heavy

Nobody decides to make a landing page heavy. It accumulates: one addition per campaign, and nothing ever removed when the campaign ends.

Marketers 6 min read

₩270,000 · Join the program