Clipping Regions

clip() intersects the clipping region with the current path, or with a Path2D and an optional 'evenodd' fill rule, and confines all later drawing to it. Clips only shrink; to widen one, restore() a state saved before it.

A 4.6-star rating: five stars drawn twice, the second copy clipped to 4.6 starsHTML
<canvas id="c" style="width:600px; height:56px"></canvas><script src="hidpi.js"></script>
<script>
  const ctx = hidpi(c);
  const star = new Path2D('M12 2.6 14.5 9.2 21.5 9.5 16 13.9 17.9 20.7 12 16.8 6.1 20.7 ' +
    '8 13.9 2.5 9.5 9.5 9.2Z');                                // the star of Section 1.3.2
  const stars = color => [0, 1, 2, 3, 4].forEach(i => {
    ctx.save();  ctx.translate(10 + i * 52, 4);  ctx.scale(2, 2);
    ctx.fillStyle = color;  ctx.fill(star);  ctx.restore(); });
  stars('#e0d8c8');                                            // pale base row
  ctx.save();  ctx.beginPath();  ctx.rect(0, 0, 10 + 4 * 52 + 0.6 * 48, 56);  ctx.clip();
  stars('#e09a10');  ctx.restore();                            // saffron, clipped
  ctx.font = '20px Georgia';  ctx.fillText('4.6  The Quiet Harbor', 290, 36);
</script>
Browser output of Listing 2.11
Browser output of 11

The clip spans four stars on a 52-unit pitch plus 60 percent of the fifth star's 48-unit box, the same arithmetic as the SVG clip path of Clipping and Masking. A clip is a hard edge; for a soft fade, use compositing.

A star rating filled to any fraction with a clipping regionHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; font: 13px system-ui, sans-serif; background: #fafaf7; }
  canvas { width: 100%; max-width: 600px; height: 190px; display: block; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="c"></canvas>
<p>Move the pointer across the first row to set a rating.</p>
<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 c = document.getElementById('c');
  const ctx = hidpi(c);
  const star = new Path2D('M12 2.6 14.5 9.2 21.5 9.5 16 13.9 17.9 20.7 12 16.8 6.1 20.7 ' +
                          '8 13.9 2.5 9.5 9.5 9.2Z');     // a 24-unit star
  const books = [['The Quiet Harbor', 4.6], ['Salt and Saffron', 4.8], ['Small Steps to Big Summits', 4.1]];

  function stars(y, color) {
    for (let i = 0; i < 5; i++) {
      ctx.save();  ctx.translate(10 + i * 52, y);  ctx.scale(2, 2);
      ctx.fillStyle = color;  ctx.fill(star);  ctx.restore();
    }
  }
  function rating(y, value, title) {
    stars(y, '#e0d8c8');                                    // pale base row
    ctx.save();
    ctx.beginPath();                                        // clip to the rated width
    const whole = Math.floor(value);
    ctx.rect(0, y, 10 + whole * 52 + (value - whole) * 48, 50);
    ctx.clip();                                             // later drawing stays inside
    stars(y, '#e09a10');
    ctx.restore();                                          // widen the clip again
    ctx.font = '18px Georgia';  ctx.fillStyle = '#263238';
    ctx.fillText(`${value.toFixed(1)}  ${title}`, 290, y + 32);
  }
  function draw(first) {
    ctx.clearRect(0, 0, 600, 190);
    books.forEach(([title, value], i) => rating(6 + i * 60, i === 0 ? first : value, title));
  }
  c.addEventListener('pointermove', e => {
    const b = c.getBoundingClientRect(), x = e.clientX - b.left, y = e.clientY - b.top;
    if (y < 60 && x < 270) {
      const i = Math.floor((x - 10) / 52), frac = Math.min(1, Math.max(0, (x - 10 - i * 52) / 48));
      draw(Math.max(0, Math.min(5, i + frac)));
    }
  });
  draw(4.6);
</script>