Letter Spacing

Letter Spacing and Font Kerning

ctx.letterSpacing and ctx.wordSpacing take CSS lengths such as '4px' and became Baseline (newly available) in March 2025. ctx.fontKerning ('normal' or 'none'), fontStretch and textRendering mirror their CSS namesakes but are missing in Safari 10 , so they are not Baseline.

Kerning on and off, and letter spacing, with measured widthsHTML
<canvas id="c" style="width:600px; height:106px"></canvas><script src="hidpi.js"></script>
<script>
  const ctx = hidpi(c);  ctx.fillStyle = '#1f5f8b';
  const draw = (text, y, set) => {
    ctx.save();  Object.assign(ctx, { font: '28px "Times New Roman"' }, set);
    const w = ctx.measureText(text).width;  ctx.fillText(text, 10, y);
    ctx.font = '12px sans-serif';  ctx.letterSpacing = '0px';  ctx.fillStyle = '#263238';
    ctx.fillText(`${JSON.stringify(set)}: ${w.toFixed(1)}px`, 330, y - 8);  ctx.restore();
  };
  draw('AVATAR WAVE', 30, { fontKerning: 'normal' });
  draw('AVATAR WAVE', 62, { fontKerning: 'none' });
  draw('AVATAR WAVE', 96, { letterSpacing: '4px' });
</script>
Browser output of Listing 2.15
Browser output of 15

Kerning tucks each A under its V or T, saving 20 pixels; Windows' Georgia measured the same either way, so check your font. Letter spacing follows every character, the last included, and measureText() counts it: subtract one spacing when centering. A unitless '4' is invalid and ignored.

ctx.fontKerning, letterSpacing and wordSpacing compared on the same wordsHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; font: 13px system-ui, sans-serif; background: #fafaf7; }
  canvas { width: 100%; max-width: 600px; height: 170px; display: block; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="c"></canvas>
<label>letterSpacing <input type="range" id="ls" min="0" max="12" value="4"> <span id="lsv">4px</span></label>
<script>
  function hidpi(c, r = devicePixelRatio) {
    c.width = c.clientWidth * r;  c.height = c.clientHeight * r;
    const ctx = c.getContext('2d');  ctx.scale(r, r);  return ctx;
  }
  const ctx = hidpi(document.getElementById('c'));

  // Draw a line of text with some settings and report its measured width
  function draw(text, y, settings) {
    ctx.save();
    Object.assign(ctx, { font: '28px "Times New Roman", serif', fillStyle: '#1f5f8b' }, settings);
    const w = ctx.measureText(text).width;           // measureText counts the spacing too
    ctx.fillText(text, 10, y);
    ctx.fillStyle = 'rgb(224 154 16 / 0.35)';  ctx.fillRect(10, y + 4, w, 4);
    ctx.font = '12px sans-serif';  ctx.letterSpacing = '0px';  ctx.wordSpacing = '0px';
    ctx.fillStyle = '#263238';
    ctx.fillText(`${JSON.stringify(settings)}  →  ${w.toFixed(1)}px`, 330, y - 8);
    ctx.restore();
  }
  function render(spacing) {
    ctx.clearRect(0, 0, 600, 170);
    draw('AVATAR WAVE', 36, { fontKerning: 'normal' });
    draw('AVATAR WAVE', 76, { fontKerning: 'none' });
    draw('AVATAR WAVE', 116, { letterSpacing: spacing + 'px' });   // '4' without a unit is ignored
    draw('AVATAR WAVE', 156, { wordSpacing: spacing * 3 + 'px' });
  }
  const slider = document.getElementById('ls');
  slider.addEventListener('input', () => {
    document.getElementById('lsv').textContent = slider.value + 'px';
    render(+slider.value);
  });
  render(4);
</script>